Merge branch 'dev_aliyun' into dev_cxt2

dev_auth
cxt 6 years ago
commit aa45e92e10

@ -1,69 +1,70 @@
# README
This README would normally document whatever steps are necessary to get the
application up and running.
Things you may want to cover:
* Ruby version
* System dependencies
* Configuration
* Database creation
* Database initialization
* How to run the test suite
* Services (job queues, cache servers, search engines, etc.)
* Deployment instructions
* ...
#### Jbuilder介绍
Jbuilder https://github.com/rails/jbuilder
#### Rails5 介绍
rails guide https://ruby-china.github.io/rails-guides/v5.0/
#### API设计文档
doc for api https://www.showdoc.cc/web/#/127895880302646?page_id=729221359592009
user:Hjqreturn PW:12345678
#### 测试版访问地址https://testeduplus2.educoder.net
#### 实训平台繁忙
仓库异常繁忙等级81
#### 新版域名跳转规则
新版域名要求总结testeduplus2.educoder.net/(主要提供实训、实训课堂等业务)
目前有两个域名testbdweb.educoder.net(老版:主要提供课堂、项目、个人主页、后台等服务)
要求:
1、两服务域名都应该启动提供服务
2、如果请求链接包含以下的形式则域名跳至testeduplus2.educoder.net
testeduplus2.educoder.net/shixuns
testeduplus2.educoder.net/shixuns/*
testeduplus2.educoder.net/paths
testeduplus2.educoder.net/paths/*
testeduplus2.educoder.net/myshixuns/
testeduplus2.educoder.net/tasks/*
testeduplus2.educoder.net/games/*
如果不满足上述需求的域名全部跳转至testbdweb.educoder.net
比如门户首页如果访问testeduplus2.educoder.net 应为没包含上述链接。则调制testbdweb.educoder.net
在比如testeduplus2.educoder.net /users/Hjqreturn没包含上述规则则跳转到testbdweb.educoder.net/users/Hjqreturn
# 需要重构user_extensions 相关sql语句的地方标记 REDO:Extention
# 文件上传ActiveStorage
# 新能bootsnap
# 注意事项:
# 第一次部署需要执行一些rake任务
# 配置redis地址
# README
https://www.trustie.net/issues/24719
[云上实验室] Logo、导航、底部备案信息定制化
This README would normally document whatever steps are necessary to get the
application up and running.
Things you may want to cover:
* Ruby version
* System dependencies
* Configuration
* Database creation
* Database initialization
* How to run the test suite
* Services (job queues, cache servers, search engines, etc.)
* Deployment instructions
* ...
#### Jbuilder介绍
Jbuilder https://github.com/rails/jbuilder
#### Rails5 介绍
rails guide https://ruby-china.github.io/rails-guides/v5.0/
#### API设计文档
doc for api https://www.showdoc.cc/web/#/127895880302646?page_id=729221359592009
user:Hjqreturn PW:12345678
#### 测试版访问地址https://testeduplus2.educoder.net
#### 实训平台繁忙
仓库异常繁忙等级81
#### 新版域名跳转规则
新版域名要求总结testeduplus2.educoder.net/(主要提供实训、实训课堂等业务)
目前有两个域名testbdweb.educoder.net(老版:主要提供课堂、项目、个人主页、后台等服务)
要求:
1、两服务域名都应该启动提供服务
2、如果请求链接包含以下的形式则域名跳至testeduplus2.educoder.net
testeduplus2.educoder.net/shixuns
testeduplus2.educoder.net/shixuns/*
testeduplus2.educoder.net/paths
testeduplus2.educoder.net/paths/*
testeduplus2.educoder.net/myshixuns/
testeduplus2.educoder.net/tasks/*
testeduplus2.educoder.net/games/*
如果不满足上述需求的域名全部跳转至testbdweb.educoder.net
比如门户首页如果访问testeduplus2.educoder.net 应为没包含上述链接。则调制testbdweb.educoder.net
在比如testeduplus2.educoder.net /users/Hjqreturn没包含上述规则则跳转到testbdweb.educoder.net/users/Hjqreturn
# 需要重构user_extensions 相关sql语句的地方标记 REDO:Extention
# 文件上传ActiveStorage
# 新能bootsnap
# 注意事项:
# 第一次部署需要执行一些rake任务
# 配置redis地址
# 配置gitlab/intializers/gitlab_config.yml

@ -9,8 +9,10 @@
//= require bootstrap-notify
//= require jquery.cookie.min
//= require select2
//= require moment.min
//= require jquery.cxselect
//= require bootstrap-datepicker
//= require bootstrap-datetimepicker
//= require bootstrap.viewer
//= require jquery.mloading
//= require jquery-confirm.min

@ -1,5 +1,14 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-carousels-index-page').length > 0) {
var laboratoryId = $('#carousels-container').data('laboratoryId');
var resetNo = function(){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
}
// 删除后
$(document).on('delete_success', resetNo);
// ------------ 保存链接 -----------
$('.carousels-card').on('click', '.save-data-btn', function(){
var $link = $(this);
@ -13,7 +22,7 @@ $(document).on('turbolinks:load', function() {
$link.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
url: '/admins/laboratories/' + laboratoryId + '/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { link: link, name: name },
@ -34,7 +43,7 @@ $(document).on('turbolinks:load', function() {
$checkbox.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
url: '/admins/laboratories/' + laboratoryId + '/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { status: checked },
@ -60,14 +69,12 @@ $(document).on('turbolinks:load', function() {
var insertId = $(sibling).data('id') || '';
$.ajax({
url: '/admins/carousels/drag',
url: '/admins/laboratories/' + laboratoryId + '/carousels/drag',
method: 'POST',
dataType: 'json',
data: { move_id: moveId, after_id: insertId },
success: function(data){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
resetNo();
},
error: function(res){
var data = res.responseJSON;

@ -0,0 +1,742 @@
$(document).on('turbolinks:load', function(){
if ($('body.admins-competition-settings-index-page').length > 0) {
var dateOptions = {
autoclose: true,
language: 'zh-CN',
format: 'yyyy-mm-dd',
startDate: '2017-04-01'
};
var timeOptions = {
autoclose: 1,
language: 'zh-CN',
format: 'yyyy-mm-dd hh:ii',
minuteStep: 30
};
var defineDateRangeSelect = function (element) {
var options = $.extend({inputs: $(element).find('.start-date, .end-date')}, dateOptions);
$(element).datepicker(options);
$(element).find('.start-date').datepicker().on('changeDate', function (e) {
$(element).find('.end-date').datepicker('setStartDate', e.date);
});
};
$(".competition-start-end-date .start-date").datetimepicker(timeOptions);
$(".competition-start-end-date .end-date").datetimepicker(timeOptions);
$(".nav-setting-form .enroll_end_time").datetimepicker(timeOptions);
$(".stage-update-form .section-start-time").datetimepicker(timeOptions);
$(".stage-update-form .section-end-time").datetimepicker(timeOptions);
defineDateRangeSelect('.teaching-mode-date');
// defineTimeRangeSelect('.competition-start-end-date');
var $basicForm = $('form.basic-setting-form');
$basicForm.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
name: "required",
subTitle: "required",
startTime: "required",
endTime: "required",
mode: "required",
identifier: "required"
}
});
// 保存按钮
$basicForm.on('click', ".submit-btn", function () {
$basicForm.find('.submit-btn').attr('disabled', 'disabled');
$basicForm.find('.error').html('');
var valid = $basicForm.valid();
if ($("input[name='mode']:checked").val() == 2) {
var $courseId = $("input[name='course_id']");
if ($courseId.val() === undefined || $courseId.val().length === 0) {
$courseId.addClass('danger text-danger');
valid = false;
} else {
$courseId.removeClass('danger text-danger');
}
} else if ($("input[name='mode']:checked").val() == 3) {
var $techStartTime = $("input[name='teach_start_time']");
var $techEndTime = $("input[name='teach_end_time']");
if ($techStartTime.val() === undefined || $techStartTime.val().length === 0) {
$techStartTime.addClass('danger text-danger');
valid = false;
} else {
$techStartTime.removeClass('danger text-danger');
}
if ($techEndTime.val() === undefined || $techEndTime.val().length === 0) {
$techEndTime.addClass('danger text-danger');
valid = false;
} else {
$techEndTime.removeClass('danger text-danger');
}
} else {
$("input[name='course_id']").removeClass('danger text-danger');
$("input[name='teach_start_time']").removeClass('danger text-danger');
$("input[name='teach_end_time']").removeClass('danger text-danger');
}
if (!valid) return;
$.ajax({
method: 'POST',
dataType: 'json',
url: $basicForm.attr('action'),
data: new FormData($basicForm[0]),
processData: false,
contentType: false,
success: function (data) {
$.notify({message: '保存成功'});
// window.location.reload();
},
error: function (res) {
var data = res.responseJSON;
$basicForm.find('.error').html(data.message);
},
complete: function () {
$basicForm.find('.submit-btn').attr('disabled', false);
}
});
});
var selectOptions = {
theme: 'bootstrap4',
placeholder: '请输入要添加的单位名称',
multiple: true,
minimumInputLength: 1,
ajax: {
delay: 500,
url: '/api/schools/for_option.json',
dataType: 'json',
data: function(params){
return { keyword: params.term };
},
processResults: function(data){
return { results: data.schools }
}
},
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.name || item.text;
},
templateSelection: function(item){
return item.name || item.text;
}
};
$('.sponsor-select').select2(selectOptions);
$('.allow-school-select').select2(selectOptions);
$('.manager-select').select2({
theme: 'bootstrap4',
placeholder: '请输入要添加的管理员姓名',
multiple: true,
minimumInputLength: 1,
ajax: {
delay: 500,
url: '/admins/users',
dataType: 'json',
data: function(params){
return { keyword: params.term };
},
processResults: function(data){
return { results: data.users }
}
},
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return $("<div class='row px-0'><span class='col-3'>" + item.real_name + "</span><span class='col-5 font-12'>" + item.school_name + "</span><span class='col-4 font-12'>" + item.hidden_phone + "</span></div>");
},
templateSelection: function(item){
if (item.id) {
}
return item.real_name || item.text;
}
});
// 排行榜
//链接
$(".nav-setting-form").on("click",".add_linkBtn",function () {
var length=$(".nav-setting-form").find(".linkFormItem").length + 1;
var html='<div class="row mt-2 align-items-center linkFormItem">\n' +
' <div class="col-1 text-right">\n' +
' <label class="checkbox checkbox-primary mt-1">\n' +
' <input type="checkbox" name="navbar[][hidden]" value="0" hidden class="font-16" checked="checked">\n' +
' <input type="checkbox" value="0" class="font-16 module_hidden" checked="checked">\n' +
' </label>\n' +
' </div>\n' +
' <div class="col-md-label mt-1"><input type="hidden" value="md" name="navbar[][module_type]">\n' +
' <input type="text" name="navbar[][name]" value="" class="form-control" placeholder="模块名称"></div>\n' +
' <div class="col-md-1 mt-1"><input type="text" name="navbar[][position]" value="" class="form-control" placeholder="位置"></div>\n' +
' <div class="col-md-3 mt-1"><input type="text" name="navbar[][url]" value="" class="form-control" placeholder="请输入资料下载地址"></div>\n' +
' <a class="mt-1 btn btn-primary waves-effect waves-light btn-xs setBtn_s add_linkBtn" href="javascript:void(0)">+</a>\n' +
' <a class="mt-1 btn btn-icon waves-effect btn-default waves-light setBtn_s ml10 del_linkBtn" href="javascript:void(0)">×</a>\n' +
' </div>';
$(this).parents(".linkFormItem").after(html);
});
$(".nav-setting-form").on("click", ".del_linkBtn", function () {
$(this).parents(".linkFormItem").remove();
});
//有关报名要求
$(".addRequireBtn").on("click",function () {
var length=$("#requireForm").find(".requireForm_item").length + 1;
var html='<div class="row mt-2 mb-4 requireForm_item">\n' +
' <div class="col-1 text-right">&nbsp;&nbsp;</div>\n' +
' <div class="col-1 text-left mt-1">\n' +
' <input type="text" class="form-control" name="competition_staffs[][minimum]" value="0">\n' +
' </div>\n' +
' <span class="mt-2">~</span>\n' +
' <div class="col-1 mt-1">\n' +
' <input type="text" class="form-control" name="competition_staffs[][maximum]" value="1">\n' +
' </div>\n' +
' <span class="mt-2">人</span>\n' +
' <div class="col-2 mt-1">\n' +
' <select class="form-control" name="competition_staffs[][category]">\n' +
' <option value="student">学生</option>\n' +
' <option value="teacher">教师</option>\n' +
' </select>\n' +
' </div>\n' +
' <div class="col-2 mt-1">\n' +
' <label class="radio checkbox-primary mt-1" value="require_'+length+'_1">\n' +
' <input id="require_'+length+'_1" class="mutiple-limited-radio" value="false" checked name="competition_staffs[][mutiple_limited]" type="checkbox">\n' +
' <label for="require_'+length+'_1">可多次报名</label>\n' +
' </label>\n' +
' </div>\n' +
' <div class="col-2 mt-1">\n' +
' <label class="radio checkbox-primary mt-1" value="require_'+length+'_2">\n' +
' <input id="require_'+length+'_2" class="mutiple-limited-radio" value="true" name="competition_staffs[][mutiple_limited]" type="checkbox">\n' +
' <label for="require_'+length+'_2">不可多次报名</label>\n' +
' </label>\n' +
' <a href="javascript:void(0)" class="ml20 delRequrieBtn">\n' +
' <i class="fa fa-times-circle font-20 color-grey-c"></i>\n' +
' </a>\n' +
' </div>\n' +
' </div>';
$("#requireForm").append(html);
});
$("#requireForm").on("click",".delRequrieBtn",function () {
$(this).parents(".requireForm_item").remove();
});
$('.nav-setting-form').on('click', '.module_hidden', function(){
var checkEle = $(this);
if (checkEle.is(':checked')) {
checkEle.prev().val(0);
} else {
checkEle.prev().val(1);
}
});
$('.competition-staff-settings').on('click', '.mutiple-limited-radio', function(){
var radio = $(this);
if (radio.is(':checked')) {
radio.parent().parent().siblings().find('.mutiple-limited-radio').attr('checked', false)
} else {
radio.parent().parent().siblings().find('.mutiple-limited-radio').attr('checked', true)
}
});
var $navForm = $('form.nav-setting-form');
$navForm.on('click', ".submit-btn", function () {
$navForm.find('.submit-btn').attr('disabled', 'disabled');
$navForm.find('.error').html('');
var valid = $navForm.valid();
if (!valid) return;
$.ajax({
method: 'POST',
dataType: 'json',
url: $navForm.attr('action'),
data: new FormData($navForm[0]),
processData: false,
contentType: false,
success: function (data) {
$.notify({message: '保存成功'});
// window.location.reload();
},
error: function (res) {
var data = res.responseJSON;
$navForm.find('.error').html(data.message);
},
complete: function () {
$navForm.find('.submit-btn').attr('disabled', false);
}
});
});
// 排行榜设置
//删除小阶段
$("#large_panel").on("click",".small_panel_item_del",function () {
var list = $(this).parents(".small_panel");
$(this).parents(".small_panel_item").remove();
for(var i=0;i < $(list).find(".subName").length;i++){
console.log(i);
$(list).find(".subName").eq(i).html("第"+parseInt(i+1)+"阶段");
}
});
// $('form.stage-update-form').validate({
// errorElement: 'span',
// errorClass: 'danger text-danger',
// rules: {
// stage_name: "required",
// "stage[][start_time]": "required",
// "stage[][end_time]": "required",
// "stage[][mission_count]": {
// required: true,
// min: 1
// },
// "stage[][entry]": {
// required: true,
// min: 1
// },
// score_rate: {
// required: true,
// range: [0, 100]
// }
// },
// messages: {
// "stage[][mission_count]": {
// min: ">=1"
// },
// "stage[][entry]": {
// min: ">=1"
// },
// }
// });
$('.competition-chart-setting').on('click', ".update-stage", function () {
var updateForm = $(this).parents("form");
$(this).attr('disabled', 'disabled');
updateForm.find('.error').html('');
// var valid = updateForm.valid();
var valid = true;
var $stageName = updateForm.find('input[name="stage_name"]');
if($stageName.val() === undefined || $stageName.val().length === 0){
$stageName.addClass('danger text-danger');
valid = false;
} else {
$stageName.removeClass('danger text-danger');
}
var $scoreRate = updateForm.find('input[name="score_rate"]');
if($scoreRate.val() === undefined || $scoreRate.val().length === 0){
$scoreRate.addClass('danger text-danger');
valid = false;
} else if (parseInt($scoreRate.val()) > 100 || parseInt($scoreRate.val()) < 0) {
$scoreRate.addClass('danger text-danger');
$scoreRate.after('<span class="danger text-danger">0-100之间的数值</span>');
valid = false;
} else {
$scoreRate.removeClass('danger text-danger');
$scoreRate.siblings().remove();
}
updateForm.find('input[name="stage[][start_time]"]').each(function(_, e){
var $ele = $(e);
if($ele.val() === undefined || $ele.val().length === 0){
$ele.addClass('danger text-danger');
valid = false;
} else {
$ele.removeClass('danger text-danger');
}
});
updateForm.find('input[name="stage[][end_time]"]').each(function(_, e){
var $ele = $(e);
if($ele.val() === undefined || $ele.val().length === 0){
$ele.addClass('danger text-danger');
valid = false;
} else {
$ele.removeClass('danger text-danger');
}
});
updateForm.find('input[name="stage[][mission_count]"]').each(function(i, e){
var $ele = $(e);
var $entry = updateForm.find('input[name="stage[][entry]"]').eq(i);
if($ele.val() === undefined || $ele.val().length === 0){
$ele.addClass('danger text-danger');
valid = false;
} else if (parseInt($ele.val()) < 1) {
$ele.addClass('danger text-danger');
$ele.after('<span class="danger text-danger">大于等于1</span>');
valid = false;
} else if (parseInt($ele.val()) > parseInt($entry.val())) {
$ele.addClass('danger text-danger');
$ele.after('<span class="danger text-danger">不能大于总任务数</span>');
valid = false;
} else {
$ele.removeClass('danger text-danger');
$ele.siblings().remove();
}
});
updateForm.find('input[name="stage[][entry]"]').each(function(_, e){
var $ele = $(e);
if($ele.val() === undefined || $ele.val().length === 0){
$ele.addClass('danger text-danger');
valid = false;
} else if (parseInt($ele.val()) < 1) {
$ele.addClass('danger text-danger');
$ele.after('<span class="danger text-danger">大于等于1</span>');
valid = false;
} else {
$ele.removeClass('danger text-danger');
$ele.siblings().remove();
}
});
updateForm.find('input[name="stage[][identifiers][]"]').each(function(_, e){
var $ele = $(e);
if($ele.val() === undefined || $ele.val().length === 0){
$ele.addClass('danger text-danger');
valid = false;
} else {
$ele.removeClass('danger text-danger');
}
});
if (!valid) return;
updateForm.find('input[name="stage[][mission_count]"]').each(function(_, e){
var $missionCount = $(e);
var $entryCount = $(e).parents("div.row").find('input[name="stage[][mission_count]"]');
if(parseInt($missionCount.val()) > parseInt($entryCount.val()) ){
$missionCount.addClass('danger text-danger');
$missionCount.after('<span class="danger text-danger">不能大于总任务数</span>');
valid = false;
} else {
$missionCount.removeClass('danger text-danger');
$missionCount.siblings().remove();
}
});
$.ajax({
method: 'POST',
dataType: 'json',
url: updateForm.attr('action'),
data: new FormData(updateForm[0]),
processData: false,
contentType: false,
success: function (data) {
$.notify({message: '保存成功'});
window.location.reload();
},
error: function (res) {
var data = res.responseJSON;
$navForm.find('.error').html(data.message);
},
complete: function () {
$navForm.find('.submit-btn').attr('disabled', false);
}
});
});
$(".competition-chart-stages").on("click", ".add-new-tab", function () {
if($(".new-stage-form").length > 0){
alert("请先保存上一个tab");
} else {
var count = parseInt($("#large_panel").find(".large_panel_part").length)+1;
var html = '<form class="stage-update-form new-stage-form flex-1" action="/admins/competitions/'+$(this).attr("data-competition-id")+'/competition_stages" accept-charset="UTF-8" data-remote="true" method="post">' +
'<div class="large_panel_part" attr_line="'+count+'"><div class="row d-flex mt-3">\n' +
' <span class="col-1 mt-2">tab标题</span>\n' +
' <div class="col-2 no_padding">\n' +
' <input type="text" class="form-control" name="stage_name"/>\n' +
' </div>\n' +
' <span class="col-1 text-right mt-2 no_padding">总排行榜占比:</span>\n' +
' <div class="col-1 no_padding">\n' +
' <input type="number" class="form-control" name="score_rate" value="100"/>\n' +
' </div><span class=" mt-2">%</span>\n' +
' <div class="flex-1">\n' +
' <a href="javascript:void(0)"class="btn btn-outline-primary export-action ml20 add-task-sub">新增子阶段</a>\n' +
' </div>\n' +
' <a href="javascript:void(0)" class="btn btn-default ml20" onclick="Del_tab(this)">删除</a>\n' +
' <a href="javascript:void(0)" class="btn btn-outline-primary update-stage export-action ml20">保存</a>\n' +
' </div>\n' +
' <div id="small_panel_'+count+'" class="small_panel">\n' +
' <div class="row d-flex small_panel_item" attr_line="sub_new_new" count="1">\n' +
' <span class="col-1 mt-2 subName">第1阶段</span>\n' +
' <div class="flex-1">\n' +
' <div class="row">\n' +
' <span class="mt-2 ml20">有效时间:</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <input type="text" name="stage[][start_time]" id="stage__start_time" value="" autocomplete="off" class="section-start-time form-control" placeholder="有效开始时间">\n' +
' </div>\n' +
' <span class="mt-2">~</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <input type="text" name="stage[][end_time]" id="stage__end_time" value="" autocomplete="off" class="section-end-time form-control" placeholder="有效结束时间">\n' +
' </div>\n' +
' <span class="col-2 text-right mt-2 no_padding">总任务数:</span>\n' +
' <div class="col-1 no_padding input_small">\n' +
' <input type="number" class="form-control" onchange="change_total(this)" value="3" name="stage[][entry]">\n' +
' </div>\n' +
' <span class="col-1 text-right mt-2 no_padding">成绩来源:</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <select class="form-control" name="stage[][score_source]">\n' +
' <option value="0">经验值</option>\n' +
' <option value="1">预测准确率</option>\n' +
' </select>\n' +
' </div>\n' +
' </div>\n' +
' <div class="row mt-2" id="task_Input_sub_new_new">\n' +
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务1</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务2</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务3</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' <span>\n' +
' <a href="javascript:void(0)" class="btn btn-default ml20 small_panel_item_del">删除</a>\n' +
' </span>\n' +
' </div>\n' +
'</div></div></form>';
$("#large_panel").append(html);
$(".stage-update-form .section-start-time").datetimepicker(timeOptions);
$(".stage-update-form .section-end-time").datetimepicker(timeOptions);
}
});
//新增子阶段
$(".competition-chart-stages").on("click", ".add-task-sub", function () {
var index = $(this).parents(".large_panel_part").attr("attr_line");
var count= 0;
console.log("sdfsf");
console.log($("#small_panel_"+index).find(".small_panel_item").length > 0);
if($("#small_panel_"+index).find(".small_panel_item").length > 0){
count = parseInt($("#small_panel_"+index).find(".small_panel_item").last().attr("count")) + 1;
console.log($("#small_panel_"+index).find(".small_panel_item").last().attr("count"));
}else{
count = 1;
}
var showCount=parseInt($("#small_panel_"+index).find(".small_panel_item").length) + 1;
var html='<div class="row d-flex small_panel_item" attr_line="sub_'+index+'_'+count+'" count="'+count+'">\n' +
' <span class="col-1 mt-2 subName">第'+showCount+'阶段</span>\n' +
' <div class="flex-1">\n' +
' <div class="row">\n' +
' <span class="mt-2 ml20">有效时间:</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <input type="text" name="stage[][start_time]" id="stage__start_time" value="" autocomplete="off" class="section-start-time form-control" placeholder="有效开始时间">\n' +
' </div>\n' +
' <span class="mt-2">~</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <input type="text" name="stage[][end_time]" id="stage__end_time" value="" autocomplete="off" class="section-end-time form-control" placeholder="有效结束时间">\n' +
' </div>\n' +
' <span class="col-2 text-right mt-2 no_padding">总任务数:</span>\n' +
' <div class="col-1 no_padding input_small">\n' +
' <input type="number" class="form-control" onchange="change_total(this)" value="3" name="stage[][entry]">\n' +
' </div>\n' +
' <span class="col-1 text-right mt-2 no_padding">成绩来源:</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <select class="form-control" name="stage[][score_source]">\n' +
' <option value="0">经验值</option>\n' +
' <option value="1">预测准确率</option>\n' +
' </select>\n' +
' </div>\n' +
' </div>\n' +
' <div class="row mt-2" id="task_Input_sub_'+index+'_'+count+'">\n'+
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务1</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务2</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务3</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' <span>\n' +
' <a href="javascript:void(0)" class="btn btn-default ml20 small_panel_item_del">删除</a>\n' +
' </span>\n' +
' </div>';
$("#small_panel_"+index).append(html);
$(".stage-update-form .section-start-time").datetimepicker(timeOptions);
$(".stage-update-form .section-end-time").datetimepicker(timeOptions);
});
}
});
//添加主办方或者开放范围
function addSponsor(item){
var html='<div class="sponsor_label">\n' +
' <input type="hidden" value="school_id" />\n' +
' <span>caicai</span>\n' +
' <a href="javascript:void(0)" onclick="del_sponsor(this)">×</a>\n' +
' </div>';
$(item).parents(".sponsorPanel").append(html);
}
//删除
function del_sponsor(item){
$(item).parents(".sponsor_label").remove();
}
// 小阶段修改总任务数
function change_total(item) {
var count=parseInt($(item).val());
var index = $(item).parents(".small_panel_item").attr("attr_line");
var indexLarge = $(item).parents(".large_panel_part").attr("attr_line");
console.log(indexLarge);
console.log(index);
var divCount=parseInt($("#task_Input_"+index).find(".task_Input_div").length);
var html = "";
if(count > divCount){
for(var i=0;i < count-divCount ;i++){
html+='<div class="col-4 row task_Input_div"><span class="col-3 text-center mt-3">任务'+(divCount+i+1)+'</span>\n' +
'<div class="col-8">\n' +
'<input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
'</div>\n' +
'</div>';
}
$("#task_Input_"+index).append(html);
}else{
var delCount = divCount - count ;
console.log(divCount);
console.log(count);
var _max=parseInt($("#task_Input_"+index).find(".task_Input_div:last").index());
console.log(_max);
var _get= _max - delCount;
console.log(_get);
if(count == 0){
$("#task_Input_"+index).empty();
}else{
$("#task_Input_"+index).find(".task_Input_div:gt("+_get+")").remove();
}
}
}
//删除tab
function Del_tab(item) {
$(item).parents(".large_panel_part").remove();
}
//新增tab
function addNewTab(competition_id) {
if($(".new-stage-form").length > 0){
alert("请先保存上一个tab");
} else {
var count = parseInt($("#large_panel").find(".large_panel_part").length)+1;
var html = '<form class="stage-update-form new-stage-form flex-1" action="/admins/competitions/'+competition_id+'/competition_stages" accept-charset="UTF-8" data-remote="true" method="post">' +
'<div class="large_panel_part" attr_line="'+count+'"><div class="row d-flex mt-3">\n' +
' <span class="col-1 mt-2">tab标题</span>\n' +
' <div class="col-2 no_padding">\n' +
' <input type="text" class="form-control" name="stage_name"/>\n' +
' </div>\n' +
' <span class="col-1 text-right mt-2 no_padding">总排行榜占比:</span>\n' +
' <div class="col-1 no_padding">\n' +
' <input type="number" class="form-control" name="score_rate" value="100"/>\n' +
' </div><span class=" mt-2">%</span>\n' +
' <div class="flex-1">\n' +
' <a href="javascript:void(0)"class="btn btn-outline-primary export-action ml20 add-task-sub">新增子阶段</a>\n' +
' </div>\n' +
' <a href="javascript:void(0)" class="btn btn-default ml20" onclick="Del_tab(this)">删除</a>\n' +
' <a href="javascript:void(0)" class="btn btn-outline-primary update-stage export-action ml20">保存</a>\n' +
' </div>\n' +
' <div id="small_panel_'+count+'" class="small_panel">\n' +
' <div class="row d-flex small_panel_item" attr_line="sub_new_new" count="1">\n' +
' <span class="col-1 mt-2 subName">第1阶段</span>\n' +
' <div class="flex-1">\n' +
' <div class="row">\n' +
' <span class="mt-2 ml20">有效时间:</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <input type="text" name="stage[][start_time]" id="stage__start_time" value="" autocomplete="off" class="section-start-time form-control" placeholder="有效开始时间">\n' +
' </div>\n' +
' <span class="mt-2">~</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <input type="text" name="stage[][end_time]" id="stage__end_time" value="" autocomplete="off" class="section-end-time form-control" placeholder="有效结束时间">\n' +
' </div>\n' +
' <span class="col-2 text-right mt-2 no_padding">总任务数:</span>\n' +
' <div class="col-1 no_padding input_small">\n' +
' <input type="number" class="form-control" onchange="change_total(this)" value="3" name="stage[][entry]">\n' +
' </div>\n' +
' <span class="col-1 text-right mt-2 no_padding">成绩来源:</span>\n' +
' <div class="col-2 no_padding input_middle">\n' +
' <select class="form-control" name="stage[][score_source]">\n' +
' <option value="0">经验值</option>\n' +
' <option value="1">预测准确率</option>\n' +
' </select>\n' +
' </div>\n' +
' </div>\n' +
' <div class="row mt-2" id="task_Input_sub_new_new">\n' +
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务1</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务2</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' <div class="col-4 row task_Input_div">\n' +
' <span class="col-3 text-center mt-3">任务3</span>\n' +
' <div class="col-8">\n' +
' <input type="text" class="form-control mt-2" name="stage[][identifiers][]" placeholder="请填写实训ID">\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' </div>\n' +
' <span>\n' +
' <a href="javascript:void(0)" class="btn btn-default ml20 small_panel_item_del">删除</a>\n' +
' </span>\n' +
' </div>\n' +
'</div></div></form>';
$("#large_panel").append(html);
}
}

@ -0,0 +1,73 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-competitions-index-page').length > 0) {
$('.modal.admin-upload-file-modal').on('upload:success', function(e, data){
var $imageElement = $('.competition-image-' + data.source_id);
$imageElement.attr('src', data.url);
$imageElement.show();
$imageElement.next().html('重新上传');
});
}
$(".admin-competition-list-form").on("change", '.competitions-hot-select', function () {
var s_value = $(this).get(0).checked ? 1 : 0;
var json = {};
json["hot"] = s_value;
$.ajax({
url: "/admins/competitions/hot_setting",
type: "POST",
dataType:'json',
data: json,
success: function(){
$.notify({ message: '操作成功' });
}
});
});
// ============== 新增竞赛 ===============
var $modal = $('.modal.admin-create-competition-modal');
var $form = $modal.find('form.admin-create-competition-form');
var $competitionNameInput = $form.find('input[name="competition_name"]');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
competition_name: {
required: true
}
}
});
// modal ready fire
$modal.on('show.bs.modal', function () {
$competitionNameInput.val('');
});
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
if ($form.valid()) {
var url = $form.data('url');
$.ajax({
method: 'POST',
dataType: 'json',
url: url,
data: $form.serialize(),
success: function(){
$.notify({ message: '创建成功' });
$modal.modal('hide');
setTimeout(function(){
window.location.reload();
}, 500);
},
error: function(res){
var data = res.responseJSON;
$form.find('.error').html(data.message);
}
});
}
});
});

@ -0,0 +1,9 @@
$(document).on('turbolinks:load', function() {
if($('body.admins-enroll-lists-index-page').length > 0){
var search_form = $(".search-form");
//导出
$(".competition-enroll-list-form").on("click","#enroll-lists-export",function () {
window.location.href = "/admins/competitions/"+$(this).attr("data-competition-id")+"/enroll_lists/export.xlsx?" + search_form.serialize();
});
}
});

@ -135,7 +135,7 @@ $(document).on('turbolinks:load', function() {
},
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.real_name;
return $("<span>" + item.real_name + " <span class='font-12'>" + item.school_name + ' ' + item.hidden_phone + "</span></span>");
},
templateSelection: function(item){
if (item.id) {

@ -0,0 +1,18 @@
$(document).on('turbolinks:load', function() {
$('.admin-modal-container').on('show.bs.modal', '.modal.admin-edit-subject-modal', function(){
var $modal = $('.modal.admin-edit-subject-modal');
var $form = $modal.find('form.admin-edit-subject-form');
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
var url = $form.attr('action');
$.ajax({
method: 'PATCH',
dataType: 'script',
url: url,
data: $form.serialize()
});
});
})
});

@ -4,14 +4,17 @@ $(document).on('turbolinks:load', function() {
var $form = $modal.find('form.admin-upload-file-form')
var $sourceIdInput = $modal.find('input[name="source_id"]');
var $sourceTypeInput = $modal.find('input[name="source_type"]');
var $suffixInput = $modal.find('input[name="suffix"]');
$modal.on('show.bs.modal', function(event){
var $link = $(event.relatedTarget);
var sourceId = $link.data('sourceId');
var sourceType = $link.data('sourceType');
var suffix = $link.data('suffix');
$sourceIdInput.val(sourceId);
$sourceTypeInput.val(sourceType);
if(suffix != undefined){ $suffixInput.val(suffix); }
$modal.find('.upload-file-input').trigger('click');
});
@ -48,6 +51,7 @@ $(document).on('turbolinks:load', function() {
contentType: false,
success: function(data){
$.notify({ message: '上传成功' });
$modal.find('.file-names').html('');
$modal.trigger('upload:success', data);
$modal.modal('hide');
},

@ -34,10 +34,17 @@ $(document).on('turbolinks:load', function() {
});
$('.modal.admin-upload-file-modal').on('upload:success', function(e, data){
var $imageElement = $('.shixun-image-' + data.source_id);
$imageElement.attr('src', data.url);
$imageElement.show();
$imageElement.next().html('重新上传');
if(data.suffix == '_weapp'){
var $imageElement = $('.shixun-weapp-image-' + data.source_id);
$imageElement.attr('src', data.url);
$imageElement.show();
$imageElement.next().html('重新上传');
} else {
var $imageElement = $('.shixun-image-' + data.source_id);
$imageElement.attr('src', data.url);
$imageElement.show();
$imageElement.next().html('重新上传');
}
})
}
});

@ -5,7 +5,7 @@ $(document).on('turbolinks:load', function() {
allowClear: true
});
let search_form = $(".search-form");
var search_form = $(".search-form");
//导出
$(".shixuns-list-form").on("click","#shixuns-export",function () {
window.location.href = "/admins/shixuns.xls?" + search_form.serialize();

@ -0,0 +1,107 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-subjects-index-page').length > 0) {
var $form = $('.subject-list-form');
// ************** 学校选择 *************
$form.find('.school-select').select2({
theme: 'bootstrap4',
placeholder: '请选择创建者单位',
minimumInputLength: 1,
ajax: {
delay: 500,
url: '/api/schools/for_option.json',
dataType: 'json',
data: function(params){
return { keyword: params.term };
},
processResults: function(data){
return { results: data.schools }
}
},
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.name;
},
templateSelection: function(item){
if (item.id) {
}
return item.name || item.text;
}
});
// 清空
$form.on('click', '.clear-btn', function(){
$form.find('select[name="status"]').val('');
$form.find('.school-select').val('').trigger('change');
$form.find('input[name="keyword"]').val('');
$form.find('#homepage_show').attr('checked', false);
$form.find('#excellent').attr('checked', false);
$form.find('input[type="submit"]').trigger('click');
})
// 上传图片
$('.modal.admin-upload-file-modal').on('upload:success', function(e, data){
var $imageElement = $('.subject-image-' + data.source_id);
$imageElement.attr('src', data.url);
$imageElement.show();
$imageElement.next().html('重新上传');
})
// 定义状态切换监听事件
var defineStatusChangeFunc = function(doElement, undoElement, url, callback){
$('.subject-list-container').on('click', doElement, function(){
var $doAction = $(this);
var $undoAction = $doAction.siblings(undoElement);
var subjectId = $doAction.data('id');
customConfirm({
content: '确认进行该操作吗?',
ok: function(){
$.ajax({
url: '/admins/subjects/' + subjectId + url,
method: 'POST',
dataType: 'json',
success: function() {
show_success_flash();
$doAction.hide();
$undoAction.show();
if(callback && typeof callback === "function"){
callback(subjectId, url);
}
}
});
}
});
});
}
// 隐藏与取消隐藏
defineStatusChangeFunc('.hide-action', '.active-action', '/hide');
defineStatusChangeFunc('.active-action', '.hide-action', '/cancel_hide');
// 首页展示与取消首页展示
var homepageShowCallback = function(subjectId, url){
var $subjectItem = $('.subject-list-container').find('.subject-item-' + subjectId);
if(url === '/homepage_show'){
$subjectItem.find('.homepage-show-badge').show();
} else {
$subjectItem.find('.homepage-show-badge').hide();
}
}
defineStatusChangeFunc('.homepage-show-action', '.homepage-hide-action', '/homepage_show', homepageShowCallback);
defineStatusChangeFunc('.homepage-hide-action', '.homepage-show-action', '/cancel_homepage_show', homepageShowCallback);
// 设为金课与取消金课
var excellentCallback = function(subjectId, url){
var $subjectItem = $('.subject-list-container').find('.subject-item-' + subjectId);
if(url === '/excellent'){
$subjectItem.find('.excellent-badge').show();
} else {
$subjectItem.find('.excellent-badge').hide();
}
}
defineStatusChangeFunc('.excellent-action', '.cancel-excellent-action', '/excellent', excellentCallback);
defineStatusChangeFunc('.cancel-excellent-action', '.excellent-action', '/cancel_excellent', excellentCallback);
}
});

@ -0,0 +1,73 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-user-statistics-index-page').length > 0) {
var $form = $('.user-statistic-list-form');
// ************** 学校选择 *************
var matcherFunc = function(params, data){
if ($.trim(params.term) === '') {
return data;
}
if (typeof data.text === 'undefined') {
return null;
}
if (data.name && data.name.indexOf(params.term) > -1) {
var modifiedData = $.extend({}, data, true);
return modifiedData;
}
// Return `null` if the term should not be displayed
return null;
}
var defineSchoolSelect = function (schools) {
$form.find('.school-select').select2({
theme: 'bootstrap4',
placeholder: '选择学校/单位',
minimumInputLength: 1,
data: schools,
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.name;
},
templateSelection: function(item){
if (item.id) {
$form.find('#school_id').val(item.id);
}
return item.name || item.text;
},
matcher: matcherFunc
});
};
// 初始化学校选择器
$.ajax({
url: '/api/schools/for_option.json',
dataType: 'json',
type: 'GET',
success: function(data) {
defineSchoolSelect(data.schools);
}
});
// 清空
$form.on('click', '.clear-btn', function(){
$form.find('select[name="date"]').val('');
$form.find('.school-select').val('').trigger('change');
$form.find('input[type="submit"]').trigger('click');
})
// 导出
$('.export-action').on('click', function(){
var form = $(".user-statistic-list-form .search-form")
var exportLink = $(this);
var date = form.find("select[name='date']").val();
var schoolId = form.find('input[name="school_id"]').val();
var url = exportLink.data("url").split('?')[0] + "?date=" + date + "&school_id=" + schoolId;
window.open(url);
});
}
});

@ -15,15 +15,19 @@ $(document).on('turbolinks:load', function(){
var $unlockAction = $lockAction.siblings('.unlock-action');
var userId = $lockAction.data('id');
$.ajax({
url: '/admins/users/' + userId + '/lock',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$lockAction.hide();
$unlockAction.show();
customConfirm({
content: '确认加锁吗?',
ok: function(){
$.ajax({
url: '/admins/users/' + userId + '/lock',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$lockAction.hide();
$unlockAction.show();
}
});
}
});
});
@ -34,17 +38,21 @@ $(document).on('turbolinks:load', function(){
var $lockAction = $unlockAction.siblings('.lock-action');
var userId = $unlockAction.data('id');
$.ajax({
url: '/admins/users/' + userId + '/unlock',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$lockAction.show();
$unlockAction.hide();
customConfirm({
content: '确认解锁吗?',
ok: function () {
$.ajax({
url: '/admins/users/' + userId + '/unlock',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$lockAction.show();
$unlockAction.hide();
}
});
}
});
})
});
// active user
@ -54,16 +62,35 @@ $(document).on('turbolinks:load', function(){
var $lockAction = $activeAction.siblings('.lock-action');
var userId = $activeAction.data('id');
customConfirm({
content: '确认激活吗?',
ok: function () {
$.ajax({
url: '/admins/users/' + userId + '/unlock',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$activeAction.hide();
$lockAction.show();
$unlockAction.hide();
}
});
}
})
});
// reset user login times
$('.users-list-container').on('click', '.reset-login-times-action', function(){
var $action = $(this);
var userId = $action.data('id');
$.ajax({
url: '/admins/users/' + userId + '/unlock',
url: '/admins/users/' + userId + '/reset_login_times',
method: 'POST',
dataType: 'json',
success: function() {
showSuccessNotify();
$activeAction.hide();
$lockAction.show();
$unlockAction.hide();
}
});
});

@ -0,0 +1,124 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-weapp-adverts-index-page').length > 0) {
var resetNo = function(){
$('#adverts-container .advert-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
}
// ------------ 保存链接 -----------
$('.adverts-card').on('click', '.save-data-btn', function(){
var $link = $(this);
var id = $link.data('id');
var link = $('.advert-item-' + id).find('.link-input').val();
$link.attr('disabled', true);
$.ajax({
url: '/admins/weapp_adverts/' + id,
method: 'PATCH',
dataType: 'json',
data: { link: link },
success: function(data){
$.notify({ message: '操作成功' });
},
error: ajaxErrorNotifyHandler,
complete: function(){
$link.removeAttr('disabled');
}
})
});
// -------------- 是否在首页展示 --------------
$('.adverts-card').on('change', '.online-check-box', function(){
var $checkbox = $(this);
var id = $checkbox.data('id');
var checked = $checkbox.is(':checked');
$checkbox.attr('disabled', true);
$.ajax({
url: '/admins/weapp_adverts/' + id,
method: 'PATCH',
dataType: 'json',
data: { online: checked },
success: function(data){
$.notify({ message: '保存成功' });
var box = $('.advert-item-' + id).find('.drag');
if(checked){
box.removeClass('not_active');
}else{
box.addClass('not_active');
}
},
error: ajaxErrorNotifyHandler,
complete: function(){
$checkbox.removeAttr('disabled');
}
})
});
// ------------ 拖拽 -------------
var onDropFunc = function(el, _target, _source, sibling){
var moveId = $(el).data('id');
var insertId = $(sibling).data('id') || '';
$.ajax({
url: '/admins/weapp_adverts/drag',
method: 'POST',
dataType: 'json',
data: { move_id: moveId, after_id: insertId },
success: function(data){
resetNo();
},
error: function(res){
var data = res.responseJSON;
$.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'});
}
})
};
var ele1 = document.getElementById('adverts-container');
dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc);
// ----------- 新增 --------------
var $createModal = $('.modal.admin-add-weapp-advert-modal');
var $createForm = $createModal.find('form.admin-add-weapp-advert-form');
$createForm.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"weapp_settings_advert[image]": {
required: true
}
}
});
$createModal.on('show.bs.modal', function(event){
resetFileInputFunc($createModal.find('.img-file-input'));
$createModal.find('.file-names').html('选择文件');
});
$createModal.on('click', '.submit-btn', function() {
$createForm.find('.error').html('');
if ($createForm.valid()) {
$createForm.submit();
} else {
$createForm.find('.error').html('请选择图片');
}
});
$createModal.on('change', '.img-file-input', function(){
var file = $(this)[0].files[0];
$createModal.find('.file-names').html(file ? file.name : '请选择文件');
})
// -------------- 重新上传图片 --------------
//replace_image_url
$('.modal.admin-upload-file-modal').on('upload:success', function(e, data){
var $advertItem = $('.advert-item-' + data.source_id);
$advertItem.find('.advert-item-img img').attr('src', data.url);
})
// 删除后
$(document).on('delete_success', resetNo)
}
})

@ -0,0 +1,123 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-weapp-carousels-index-page').length > 0) {
var resetNo = function(){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
}
// ------------ 保存链接 -----------
$('.carousels-card').on('click', '.save-data-btn', function(){
var $link = $(this);
var id = $link.data('id');
var link = $('.custom-carousel-item-' + id).find('.link-input').val();
$link.attr('disabled', true);
$.ajax({
url: '/admins/weapp_carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { link: link },
success: function(data){
$.notify({ message: '操作成功' });
},
error: ajaxErrorNotifyHandler,
complete: function(){
$link.removeAttr('disabled');
}
})
});
// -------------- 是否在首页展示 --------------
$('.carousels-card').on('change', '.online-check-box', function(){
var $checkbox = $(this);
var id = $checkbox.data('id');
var checked = $checkbox.is(':checked');
$checkbox.attr('disabled', true);
$.ajax({
url: '/admins/weapp_carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { online: checked },
success: function(data){
$.notify({ message: '保存成功' });
var box = $('.custom-carousel-item-' + id).find('.drag');
if(checked){
box.removeClass('not_active');
}else{
box.addClass('not_active');
}
},
error: ajaxErrorNotifyHandler,
complete: function(){
$checkbox.removeAttr('disabled');
}
})
});
// ------------ 拖拽 -------------
var onDropFunc = function(el, _target, _source, sibling){
var moveId = $(el).data('id');
var insertId = $(sibling).data('id') || '';
$.ajax({
url: '/admins/weapp_carousels/drag',
method: 'POST',
dataType: 'json',
data: { move_id: moveId, after_id: insertId },
success: function(data){
resetNo();
},
error: function(res){
var data = res.responseJSON;
$.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'});
}
})
};
var ele1 = document.getElementById('carousels-container');
dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc);
// ----------- 新增 --------------
var $createModal = $('.modal.admin-add-weapp-carousel-modal');
var $createForm = $createModal.find('form.admin-add-weapp-carousel-form');
$createForm.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"weapp_settings_carousel[image]": {
required: true
}
}
});
$createModal.on('show.bs.modal', function(event){
resetFileInputFunc($createModal.find('.img-file-input'));
$createModal.find('.file-names').html('选择文件');
});
$createModal.on('click', '.submit-btn', function() {
$createForm.find('.error').html('');
if ($createForm.valid()) {
$createForm.submit();
} else {
$createForm.find('.error').html('请选择图片');
}
});
$createModal.on('change', '.img-file-input', function(){
var file = $(this)[0].files[0];
$createModal.find('.file-names').html(file ? file.name : '请选择文件');
})
// -------------- 重新上传图片 --------------
//replace_image_url
$('.modal.admin-upload-file-modal').on('upload:success', function(e, data){
var $carouselItem = $('.custom-carousel-item-' + data.source_id);
$carouselItem.find('.custom-carousel-item-img img').attr('src', data.url);
})
// 删除后
$(document).on('delete_success', resetNo)
}
})

File diff suppressed because it is too large Load Diff

@ -64,4 +64,12 @@ function customConfirm(opts){
}
}
return $.confirm($.extend({}, defaultOpts, opts))
}
function show_success_flash(message){
$.notify({
message: message || '操作成功'
},{
type: 'success'
});
}

@ -0,0 +1,92 @@
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require jquery3
//= require popper
//= require bootstrap-sprockets
//= require jquery.validate.min
//= require additional-methods.min
//= require bootstrap-notify
//= require jquery.cookie.min
//= require select2
//= require jquery.cxselect
//= require bootstrap-datepicker
//= require bootstrap-datetimepicker
//= require bootstrap.viewer
//= require jquery.mloading
//= require jquery-confirm.min
//= require common
//= require echarts
//= require codemirror/lib/codemirror
//= require codemirror/mode/shell/shell
//= require editormd/editormd
//= require editormd/languages/zh-tw
//= require dragula/dragula
//= require_tree ./i18n
//= require_tree ./cooperative
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'));
}
});
// ******** select2 global config ********
$.fn.select2.defaults.set('theme', 'bootstrap4');
$.fn.select2.defaults.set('language', 'zh-CN');
Turbolinks.setProgressBarDelay(200);
$.notifyDefaults({
type: 'success',
z_index: 9999,
delay: 2000
});
$(document).on('turbolinks:load', function(){
$('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' });
$('[data-toggle="popover"]').popover();
// 图片查看大图
$('img.preview-image').bootstrapViewer();
// flash alert提示框自动关闭
if($('.cooperative-alert-container .alert').length > 0){
setTimeout(function(){
$('.cooperative-alert-container .alert:not(.alert-danger)').alert('close');
}, 2000);
setTimeout(function(){
$('.cooperative-alert-container .alert.alert-danger').alert('close');
}, 5000);
}
});
$(document).on("turbolinks:before-cache", function () {
$('[data-toggle="tooltip"]').tooltip('hide');
$('[data-toggle="popover"]').popover('hide');
});
// var progressBar = new Turbolinks.ProgressBar();
// $(document).on('ajax:send', function(event){
// console.log('ajax send', event);
// progressBar.setValue(0)
// progressBar.show()
// });
//
// $(document).on('ajax:complete', function(event){
// console.log('ajax complete', event);
// progressBar.setValue(1)
// progressBar.hide() // 分页时不触发,奇怪
// });
// $(document).on('ajax:success', function(event){
// console.log('ajax success', event);
// });
// $(document).on('ajax:error', function(event){
// console.log('ajax error', event);
// });
$(function () {
});

@ -0,0 +1,130 @@
$(document).on('turbolinks:load', function() {
if ($('body.cooperative-carousels-index-page').length > 0) {
var resetNo = function(){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
}
// 删除后
$(document).on('delete_success', resetNo);
// ------------ 保存链接 -----------
$('.carousels-card').on('click', '.save-data-btn', function(){
var $link = $(this);
var id = $link.data('id');
var link = $('.custom-carousel-item-' + id).find('.link-input').val();
var name = $('.custom-carousel-item-' + id).find('.name-input').val();
if(!name || name.length == 0){
$.notify({ message: '名称不能为空' },{ type: 'danger' });
return;
}
$link.attr('disabled', true);
$.ajax({
url: '/cooperative/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { link: link, name: name },
success: function(data){
$.notify({ message: '操作成功' });
},
error: ajaxErrorNotifyHandler,
complete: function(){
$link.removeAttr('disabled');
}
})
});
// -------------- 是否在首页展示 --------------
$('.carousels-card').on('change', '.online-check-box', function(){
var $checkbox = $(this);
var id = $checkbox.data('id');
var checked = $checkbox.is(':checked');
$checkbox.attr('disabled', true);
$.ajax({
url: '/cooperative/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { status: checked },
success: function(data){
$.notify({ message: '保存成功' });
var box = $('.custom-carousel-item-' + id).find('.drag');
if(checked){
box.removeClass('not_active');
}else{
box.addClass('not_active');
}
},
error: ajaxErrorNotifyHandler,
complete: function(){
$checkbox.removeAttr('disabled');
}
})
});
// ------------ 拖拽 -------------
var onDropFunc = function(el, _target, _source, sibling){
var moveId = $(el).data('id');
var insertId = $(sibling).data('id') || '';
$.ajax({
url: '/cooperative/carousels/drag',
method: 'POST',
dataType: 'json',
data: { move_id: moveId, after_id: insertId },
success: function(data){
resetNo();
},
error: function(res){
var data = res.responseJSON;
$.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'});
}
})
};
var ele1 = document.getElementById('carousels-container');
dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc);
// ----------- 新增 --------------
var $createModal = $('.modal.cooperative-add-carousel-modal');
var $createForm = $createModal.find('form.cooperative-add-carousel-form');
$createForm.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"portal_image[image]": {
required: true
},
"portal_image[name]": {
required: true
},
}
});
$createModal.on('show.bs.modal', function(event){
resetFileInputFunc($createModal.find('.img-file-input'));
$createModal.find('.file-names').html('选择文件');
});
$createModal.on('click', '.submit-btn', function() {
$createForm.find('.error').html('');
if ($createForm.valid()) {
$createForm.submit();
} else {
$createForm.find('.error').html('请选择图片');
}
});
$createModal.on('change', '.img-file-input', function(){
var file = $(this)[0].files[0];
$createModal.find('.file-names').html(file ? file.name : '请选择文件');
})
// -------------- 重新上传图片 --------------
//replace_image_url
$('.modal.cooperative-upload-file-modal').on('upload:success', function(e, data){
var $carouselItem = $('.custom-carousel-item-' + data.source_id);
$carouselItem.find('.custom-carousel-item-img img').attr('src', data.url);
})
}
})

@ -0,0 +1,86 @@
$(document).on('turbolinks:load', function() {
if ($('body.cooperative-laboratory-settings-edit-page, body.cooperative-laboratory-settings-update-page').length > 0) {
var $container = $('.edit-laboratory-setting-container');
var $form = $container.find('.edit_laboratory');
$('.logo-item-left').on("change", 'input[type="file"]', function () {
var $fileInput = $(this);
var file = this.files[0];
var imageType = /image.*/;
if (file && file.type.match(imageType)) {
var reader = new FileReader();
reader.onload = function () {
var $box = $fileInput.parent();
$box.find('img').attr('src', reader.result).css('display', 'block');
$box.addClass('has-img');
};
reader.readAsDataURL(file);
} else {
}
});
createMDEditor('laboratory-footer-editor', { height: 200, placeholder: '请输入备案信息' });
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
errorPlacement:function(error,element){
if(element.parent().hasClass("input-group")){
element.parent().after(error);
}else{
element.after(error)
}
},
rules: {
identifier: {
required: true,
checkSite: true
},
name: {
required: true
}
}
});
$.validator.addMethod("checkSite",function(value,element,params){
var checkSite = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
return this.optional(element)||(checkSite.test(value + '.educoder.com'));
},"域名不合法!");
$form.on('click', '.submit-btn', function(){
$form.find('.submit-btn').attr('disabled', 'disabled');
$form.find('.error').html('');
var valid = $form.valid();
$('input[name="navbar[][name]"]').each(function(_, e){
var $ele = $(e);
if($ele.val() === undefined || $ele.val().length === 0){
$ele.addClass('danger text-danger');
valid = false;
} else {
$ele.removeClass('danger text-danger');
}
});
if(!valid) return;
$.ajax({
method: 'PATCH',
dataType: 'json',
url: $form.attr('action'),
data: new FormData($form[0]),
processData: false,
contentType: false,
success: function(data){
$.notify({ message: '保存成功' });
window.location.reload();
},
error: function(res){
var data = res.responseJSON;
$form.find('.error').html(data.message);
},
complete: function(){
$form.find('.submit-btn').attr('disabled', false);
}
});
})
}
});

@ -0,0 +1,62 @@
$(document).on('turbolinks:load', function() {
if ($('body.cooperative-laboratory-users-index-page').length > 0) {
// ============= 添加管理员 ==============
var $addMemberModal = $('.cooperative-add-laboratory-user-modal');
var $addMemberForm = $addMemberModal.find('.cooperative-add-laboratory-user-form');
var $memberSelect = $addMemberModal.find('.laboratory-user-select');
$addMemberModal.on('show.bs.modal', function(event){
$memberSelect.select2('val', ' ');
});
$memberSelect.select2({
theme: 'bootstrap4',
placeholder: '请输入要添加的管理员姓名',
multiple: true,
minimumInputLength: 1,
ajax: {
delay: 500,
url: '/cooperative/users',
dataType: 'json',
data: function(params){
return { name: params.term };
},
processResults: function(data){
return { results: data.users }
}
},
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return $("<span>" + item.real_name + " <span class='font-12'>" + item.school_name + ' ' + item.hidden_phone + "</span></span>");
},
templateSelection: function(item){
if (item.id) {
}
return item.real_name || item.text;
}
});
$addMemberModal.on('click', '.submit-btn', function(){
$addMemberForm.find('.error').html('');
var memberIds = $memberSelect.val();
if (memberIds && memberIds.length > 0) {
$.ajax({
method: 'POST',
dataType: 'json',
url: '/cooperative/laboratory_users',
data: { user_ids: memberIds },
success: function(data){
if(data && data.status == 0){
show_success_flash();
$addMemberModal.modal('hide');
window.location.reload();
}
}
});
} else {
$addMemberModal.modal('hide');
}
});
}
});

@ -0,0 +1,62 @@
$(document).on('turbolinks:load', function() {
var $modal = $('.modal.cooperative-upload-file-modal');
if ($modal.length > 0) {
var $form = $modal.find('form.cooperative-upload-file-form')
var $sourceIdInput = $modal.find('input[name="source_id"]');
var $sourceTypeInput = $modal.find('input[name="source_type"]');
$modal.on('show.bs.modal', function(event){
var $link = $(event.relatedTarget);
var sourceId = $link.data('sourceId');
var sourceType = $link.data('sourceType');
$sourceIdInput.val(sourceId);
$sourceTypeInput.val(sourceType);
$modal.find('.upload-file-input').trigger('click');
});
$modal.find('.upload-file-input').on('change', function(e){
var file = $(this)[0].files[0];
if(file){
$modal.find('.file-names').html(file.name);
$modal.find('.submit-btn').trigger('click');
}
})
var formValid = function(){
if($form.find('input[name="file"]').val() == undefined || $form.find('input[name="file"]').val().length == 0){
$form.find('.error').html('请选择文件');
return false;
}
return true;
};
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
if (formValid()) {
var formDataString = $form.serialize();
$.ajax({
method: 'POST',
dataType: 'json',
url: '/cooperatives/files?' + formDataString,
data: new FormData($form[0]),
processData: false,
contentType: false,
success: function(data){
$.notify({ message: '上传成功' });
$modal.trigger('upload:success', data);
$modal.modal('hide');
},
error: function(res){
var data = res.responseJSON;
$form.find('.error').html(data.message);
}
});
}
});
}
});

@ -0,0 +1,16 @@
$(document).on('turbolinks:load', function(){
$('#sidebarCollapse').on('click', function () {
$(this).toggleClass('active');
$('#sidebar').toggleClass('active');
$.cookie('cooperative_sidebar_collapse', $(this).hasClass('active'), {path: '/cooperative'});
});
var sidebarController = $('#sidebar').data('current-controller');
if (sidebarController.length > 0) {
$('#sidebar a.active').removeClass('active');
$('#sidebar ul.collapse.show').removeClass('show');
var activeLi = $('#sidebar a[data-controller="' + sidebarController + '"]');
activeLi.addClass('active');
activeLi.parent().parent('ul.collapse').addClass('show');
}
});

@ -0,0 +1,16 @@
/**
* Simplified Chinese translation for bootstrap-datetimepicker
* Yuan Cheung <advanimal@gmail.com>
*/
;(function($){
$.fn.datetimepicker.dates['zh-CN'] = {
days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"],
daysShort: ["周日", "周一", "周二", "周三", "周四", "周五", "周六", "周日"],
daysMin: ["日", "一", "二", "三", "四", "五", "六", "日"],
months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
monthsShort: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
today: "今天",
suffix: [],
meridiem: ["上午", "下午"]
};
}(jQuery));

File diff suppressed because one or more lines are too long

@ -7,6 +7,7 @@
@import "bootstrap-datepicker.standalone";
@import "jquery.mloading";
@import "jquery-confirm.min";
@import "bootstrap-datetimepicker.min";
@import "codemirror/lib/codemirror";
@import "editormd/css/editormd.min";

@ -48,9 +48,15 @@
}
.action-container {
.action {
& > .action {
padding: 0 3px;
}
.more-action-dropdown {
.dropdown-item {
font-size: 14px;
}
}
}
/* 分页 */

@ -0,0 +1,91 @@
.admins-competition-settings-index-page {
.competition-mode-container {
.row {
height: 35px;
}
.des-row {
height: auto;
}
.form-control {
font-size: 14px;
}
//.mode-input {
// input {
// width: 40%;
// }
//}
}
.col-md-label{
-webkit-box-flex: 0;
flex: 0 0 10%;
max-width: 10%;
min-width: 30px;
padding-right: 15px;
padding-left: 15px;
position: relative;
}
.col-md-label-s{
-webkit-box-flex: 0;
flex: 0 0 30px;
padding-right: 15px;
padding-left: 15px;
position: relative;
}
.setBtn_s{
height: 35px;
line-height: 20px;
}
.sponsor_label{
border:1px solid #4CACFF;
border-radius: 5px;
background-color: rgba(76,172,255,0.3);
color: #333;
padding:0px 4px;
height: 30px;
line-height: 30px;
float: left;
margin: 4px 5px;
span{
display: block;
float: left;
height: 28px;
line-height: 28px;
margin-right: 5px;
}
a{
font-size: 18px;
float: left;
height: 28px;
line-height: 28px;
}
}
.large_panel{
padding:0px 15px;
.large_panel_part{
border-top: 1px solid #eaeaea;
}
.large_panel_part:first-child{
border:none;
}
.large_panel_part >.row ,.small_panel >.row{
border-bottom: 1px solid #eaeaea;
padding:20px 0px;
}
.small_panel{
margin-left: 20px;
}
.row:last-child{
border:none;
}
}
}

@ -31,6 +31,7 @@
display: block;
width: 80px;
height: 80px;
background: #f0f0f0;
}
&-upload {

@ -21,9 +21,12 @@
flex-direction: column;
&-logo {
padding-left: 5px;
overflow: hidden;
margin-bottom: 10px;
& > .logo-label {
display: none;
}
}
}
@ -54,7 +57,7 @@
-ms-transform: translateX(50%);
transform: translateX(50%);
}
ul ul a {
padding: 10px !important;
@ -76,6 +79,23 @@
display: flex;
flex-direction: row;
justify-content: space-between;
&-logo {
display: flex;
justify-content: space-between;
align-items: center;
& > img {
width: 40px;
height: auto;
}
& > .logo-label {
font-size: 18px;
color: darkgrey;
margin-left: 10px;
}
}
}
#sidebarCollapse {
@ -183,7 +203,7 @@
ul li a {
padding: 10px;
font-size: 0.85em;
i {
margin-right: 0;
display: block;
@ -194,7 +214,7 @@
& > ul > li > a > i {
font-size: 1.8em;
}
ul ul a {
padding: 10px !important;
}

@ -0,0 +1,60 @@
.admins-weapp-adverts-index-page {
.adverts-card {
.advert-item {
& > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
&-no {
font-size: 28px;
text-align: center;
}
&-img {
cursor: pointer;
width: 100%;
height: 60px;
& > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
}
.not_active {
background: #F0F0F0;
}
.delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
.save-url-btn {
cursor: pointer;
}
.operate-box {
display: flex;
justify-content: space-between;
align-items: center;
}
.online-check-box {
font-size: 20px;
}
.name-input {
flex: 1;
}
.link-input {
flex: 3;
}
}
}
}

@ -0,0 +1,60 @@
.admins-weapp-carousels-index-page {
.carousels-card {
.custom-carousel-item {
& > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
&-no {
font-size: 28px;
text-align: center;
}
&-img {
cursor: pointer;
width: 100%;
height: 60px;
& > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
}
.not_active {
background: #F0F0F0;
}
.delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
.save-url-btn {
cursor: pointer;
}
.operate-box {
display: flex;
justify-content: space-between;
align-items: center;
}
.online-check-box {
font-size: 20px;
}
.name-input {
flex: 1;
}
.link-input {
flex: 3;
}
}
}
}

@ -1,16 +0,0 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_tree .
*= require_self
*/

File diff suppressed because one or more lines are too long

@ -29,21 +29,30 @@ input.form-control {
.flex-1 {
flex: 1;
}
.fl{float: left}
.no_padding{padding: 0px!important;}
.font-12 { font-size: 12px !important; }
.font-14 { font-size: 14px !important; }
.font-16 { font-size: 16px !important; }
.font-18 { font-size: 18px !important; }
.font-20 { font-size: 20px !important; }
.font-24 { font-size: 24px !important; }
.padding10-5 { padding: 10px 5px;}
.width100 { width: 100%;}
.mb10 { margin-bottom: 10px ;}
.mt10 { margin-top: 10px ;}
.mr10{ margin-right: 10px; }
.ml10{ margin-left: 10px; }.ml20{ margin-left: 20px; }
.textarea-width-100{width:100%; resize: none; border: 1px solid #ccc;}
.padding10{padding: 10px;}
.padding5-10{padding: 5px 10px;}
.position-r{position:relative;}
.color-grey-c{color:#ccc}
.color-blue{color:#4CACFF}
.color-orange{color: #ff6800}
.inline-block{display:inline-block;}
.hide{display: none;}
.show{display: block;}
.input_small{flex: 0 0 6%!important;}
.input_middle{flex:0 0 13%!important;}

@ -0,0 +1,54 @@
@import "bootstrap";
@import "font-awesome-sprockets";
@import "font-awesome";
@import "select2.min";
@import "select2-bootstrap4.min";
@import "bootstrap-datepicker";
@import "bootstrap-datepicker.standalone";
@import "jquery.mloading";
@import "jquery-confirm.min";
@import "codemirror/lib/codemirror";
@import "editormd/css/editormd.min";
@import "dragula/dragula";
@import "common";
@import "cooperative/*";
body {
width: 100vw;
height: 100vh;
max-width: 100vw;
max-height: 100vh;
display: flex;
align-items: stretch;
font-size: 14px;
background: #efefef;
overflow: hidden;
}
.simple_form {
.form-group {
.collection_radio_buttons {
margin-bottom: 0px;
}
.form-check-inline {
height: calc(1.5em + 0.75rem + 2px)
}
}
}
input.form-control {
font-size: 14px;
}
.btn-default{
color: #666;
background: #e1e1e1!important;
}
.export-absolute{
right:20px;
position: absolute;
}

@ -0,0 +1,60 @@
.cooperative-carousels-index-page {
.carousels-card {
.custom-carousel-item {
& > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
&-no {
font-size: 28px;
text-align: center;
}
&-img {
cursor: pointer;
width: 100%;
height: 60px;
& > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
}
.not_active {
background: #F0F0F0;
}
.delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
.save-url-btn {
cursor: pointer;
}
.operate-box {
display: flex;
justify-content: space-between;
align-items: center;
}
.online-check-box {
font-size: 20px;
}
.name-input {
flex: 1;
}
.link-input {
flex: 3;
}
}
}
}

@ -0,0 +1,126 @@
.cooperative-body-container {
padding: 20px;
flex: 1;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-y: scroll;
& > .content {
flex: 1;
font-size: 14px;
.box {
padding: 20px;
border-radius: 5px;
background: #fff;
}
}
/* 面包屑 */
.breadcrumb {
padding-left: 5px;
font-size: 20px;
background: unset;
}
/* 内容表格 */
table {
table-layout: fixed;
td {
vertical-align: middle;
}
tr {
&.no-data {
&:hover {
color: darkgrey;
background: unset;
}
& > td {
text-align: center;
height: 300px;
}
}
}
}
.action-container {
& > .action {
padding: 0 3px;
}
.more-action-dropdown {
.dropdown-item {
font-size: 14px;
}
}
}
/* 分页 */
.paginate-container {
margin-top: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.paginate-total {
margin-bottom: 10px;
color: darkgrey;
}
.pagination {
margin-bottom: 0px;
}
}
/* 搜索表单 */
.search-form-container {
display: flex;
margin-bottom: 20px;
.search-form {
flex: 1;
* { font-size: 14px; }
select, input {
margin-right: 10px;
font-size: 14px;
}
}
}
.global-error {
color: grey;
min-height: 300px;
&-code {
font-size: 80px;
}
&-text {
font-size: 24px;
}
}
.nav-tabs {
.nav-link {
padding: 0.5rem 2rem;
}
}
.CodeMirror {
border: 1px solid #ced4da;
}
.batch-action-container {
margin-bottom: -15px;
padding: 10px 20px 0;
background: #fff;
}
}

@ -0,0 +1,76 @@
.cooperative-laboratory-settings-edit-page, .cooperative-laboratory-settings-update-page {
.edit-laboratory-setting-container {
.logo-item {
display: flex;
&-img {
display: block;
width: 80px;
height: 80px;
background: #f0f0f0;
}
&-upload {
cursor: pointer;
position: absolute;
top: 0;
width: 80px;
height: 80px;
background: #F5F5F5;
border: 1px solid #E5E5E5;
&::before {
content: '';
position: absolute;
top: 27px;
left: 39px;
width: 2px;
height: 26px;
background: #E5E5E5;
}
&::after {
content: '';
position: absolute;
top: 39px;
left: 27px;
width: 26px;
height: 2px;
background: #E5E5E5;
}
}
&-left {
position: relative;
width: 80px;
height: 80px;
&.has-img {
.logo-item-upload {
display: none;
}
&:hover {
.logo-item-upload {
display: block;
background: rgba(145, 145, 145, 0.8);
}
}
}
}
&-right {
display: flex;
flex-direction: column;
justify-content: space-between;
color: #777777;
font-size: 12px;
}
&-title {
color: #23272B;
font-size: 14px;
}
}
}
}

@ -0,0 +1,235 @@
#sidebar {
min-width: 200px;
max-width: 200px;
background: #272822;
color: #fff;
transition: all 0.5s;
overflow-y: scroll;
&::-webkit-scrollbar {
display:none
}
&.active {
min-width: 60px;
max-width: 60px;
text-align: center;
.sidebar-header {
padding: 10px;
display: flex;
flex-direction: column;
&-logo {
overflow: hidden;
margin-bottom: 10px;
& > .logo-label {
display: none;
}
}
}
ul li a {
padding: 10px;
text-align: center;
font-size: 0.85em;
display: flex;
justify-content: center;
span { display: none }
i {
margin-right: 0;
display: block;
font-size: 1.8em;
margin-bottom: 5px;
width: 30px;
height: 20px;
}
}
.dropdown-toggle::after {
top: auto;
bottom: 10px;
right: 50%;
-webkit-transform: translateX(50%);
-ms-transform: translateX(50%);
transform: translateX(50%);
}
ul ul a {
padding: 10px !important;
span { display: none }
i {
margin-left: 0px;
display: block;
font-size: 0.8em;
width: 30px;
height: 10px;
}
}
}
.sidebar-header {
padding: 20px;
background: #272822;
display: flex;
flex-direction: row;
justify-content: space-between;
&-logo {
display: flex;
justify-content: space-between;
align-items: center;
& > img {
width: 40px;
height: auto;
}
& > .logo-label {
font-size: 18px;
color: darkgrey;
margin-left: 10px;
}
}
}
#sidebarCollapse {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
text-align: right;
&.active {
width: 40px;
height: 30px;
background: #3f3f3f;
border: 1px solid grey;
border-radius: 3px;
i.fold { display: none; }
i.unfold { display: block; }
}
i.fold {
display: block;
}
i.unfold { display: none; }
}
a, a:hover, a:focus {
color: inherit;
text-decoration: none;
transition: all 0.3s;
}
& > ul > li > a > i {
width: 14px;
height: 14px;
}
ul {
&.components {
padding: 20px 0;
border-bottom: 1px solid #3f3f3f;
}
p {
color: #fff;
padding: 10px;
}
li > a {
padding: 10px;
font-size: 1em;
display: block;
text-align: left;
i {
margin-right: 10px;
font-size: 1em;
margin-bottom: 5px;
}
}
li a {
&:hover, &.active {
color: #fff;
background: #276891;
}
}
li.active > a, a[aria-expanded="true"] {
color: #fff;
//background: #276891;
}
ul a {
font-size: 0.9em !important;
padding-left: 30px !important;
background: #3f3f3f;
}
}
}
@media (max-width: 768px) {
#sidebar {
&.active {
padding: 10px 5px;
min-width: 40px;
max-width: 40px;
text-align: center;
margin-left: 0;
transform: none;
.sidebar-header {
padding: 0px;
.sidebar-header-logo {
display: none;
}
#sidebarCollapse {
width: 30px;
height: 20px;
}
}
ul li a {
padding: 10px;
font-size: 0.85em;
i {
margin-right: 0;
display: block;
margin-bottom: 5px;
}
}
& > ul > li > a > i {
font-size: 1.8em;
}
ul ul a {
padding: 10px !important;
}
}
.sidebar-header {
}
}
.dropdown-toggle::after {
top: auto;
bottom: 10px;
right: 50%;
-webkit-transform: translateX(50%);
-ms-transform: translateX(50%);
transform: translateX(50%);
}
}

@ -73,15 +73,21 @@ class AccountsController < ApplicationController
def login
@user = User.try_to_login(params[:login], params[:password])
if @user
# user is already in local database
return normal_status(-2, "违反平台使用规范,账号已被锁定") if @user.locked?
return normal_status(-2, "错误的账号或密码") unless @user.check_password?(params[:password].to_s)
else
return normal_status(-2, "错误的账号或密码") if @user.blank?
# user is already in local database
return normal_status(-2, "违反平台使用规范,账号已被锁定") if @user.locked?
login_control = LimitForbidControl::UserLogin.new(@user)
return normal_status(-2, "登录密码出错已达上限将锁定密码1小时") if login_control.forbid?
password_ok = @user.check_password?(params[:password].to_s)
unless password_ok
login_control.increment!
return normal_status(-2, "错误的账号或密码")
end
successful_authentication(@user)
login_control.clear # 重置每日密码错误次数
session[:user_id] = @user.id
end

@ -1,7 +1,7 @@
class Admins::BaseController < ApplicationController
include Admins::PaginateHelper
include Base::PaginateHelper
include Admins::RenderHelper
include Admins::ErrorRescueHandler
include Base::ErrorRescueHandler
layout 'admin'

@ -1,15 +1,17 @@
class Admins::CarouselsController < Admins::BaseController
before_action :convert_file!, only: [:create]
helper_method :current_laboratory
def index
@images = PortalImage.order(position: :asc)
@images = current_laboratory.portal_images.order(position: :asc)
end
def create
position = PortalImage.count + 1
position = current_laboratory.portal_images.count + 1
ActiveRecord::Base.transaction do
image = PortalImage.create!(create_params.merge(position: position))
image = current_laboratory.portal_images.create!(create_params.merge(position: position))
file_path = Util::FileManage.disk_filename('PortalImage', image.id)
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
@ -17,7 +19,7 @@ class Admins::CarouselsController < Admins::BaseController
end
flash[:success] = '保存成功'
redirect_to admins_carousels_path
redirect_to admins_laboratory_carousels_path(current_laboratory)
end
def update
@ -29,7 +31,7 @@ class Admins::CarouselsController < Admins::BaseController
ActiveRecord::Base.transaction do
current_image.destroy!
# 前移
PortalImage.where('position > ?', current_image.position)
current_laboratory.portal_images.where('position > ?', current_image.position)
.update_all('position = position - 1')
file_path = Util::FileManage.disk_filename('PortalImage', current_image.id)
@ -39,10 +41,10 @@ class Admins::CarouselsController < Admins::BaseController
end
def drag
move = PortalImage.find_by(id: params[:move_id])
after = PortalImage.find_by(id: params[:after_id])
move = current_laboratory.portal_images.find_by(id: params[:move_id])
after = current_laboratory.portal_images.find_by(id: params[:after_id])
Admins::DragPortalImageService.call(move, after)
Admins::DragPortalImageService.call(current_laboratory, move, after)
render_ok
rescue Admins::DragPortalImageService::Error => e
render_error(e.message)
@ -50,8 +52,12 @@ class Admins::CarouselsController < Admins::BaseController
private
def current_laboratory
@_current_laboratory ||= Laboratory.find(params[:laboratory_id])
end
def current_image
@_current_image ||= PortalImage.find(params[:id])
@_current_image ||= current_laboratory.portal_images.find(params[:id])
end
def create_params

@ -0,0 +1,33 @@
class Admins::CompetitionSettingsController < Admins::BaseController
def index
@competition = current_competition
end
def basic_setting
Admins::CompetitionBasicSettingService.call(current_competition, basic_form_params)
render_ok
end
def nav_setting
Admins::CompetitionNavSettingService.call(current_competition, nav_form_params)
render_ok
end
private
def current_competition
@_current_competition ||= Competition.find(params[:competition_id])
end
def basic_form_params
params.permit(:identifier, :name, :sub_title, :start_time, :end_time, :mode,
:identifier, :bonus, :awards_count, :description, :course_id, :teach_start_time,
:teach_end_time, sponsor_schools: [], region_schools: [], manager_ids: [])
end
def nav_form_params
params.permit(:enroll_end_time,
competition_staffs: %i[category minimum maximum mutiple_limited],
navbar: %i[module_type module_id name hidden position url])
end
end

@ -0,0 +1,190 @@
class Admins::CompetitionStagesController < Admins::BaseController
def create
Admins::CompetitionStageCreateService.call(current_competition, update_form_params)
render_ok
end
def update
Admins::CompetitionStageUpdateService.call(current_competition, update_form_params, current_stage)
render_ok
end
def destroy
current_stage.destroy!
end
def calculate_stage_score
if current_stage.max_end_time && current_stage.max_end_time < Time.now
ActiveRecord::Base.transaction do
begin
current_stage.competition_scores.destroy_all
if current_stage.competition_stage_sections.size > 0
# 是否只有老师参赛
only_teacher = current_competition.member_staff.blank?
stage_sections = current_stage.competition_stage_sections.includes(:competition_entries)
shixuns = Shixun.where(identifier: current_stage.competition_entries.pluck(:shixun_identifier).reject(&:blank?))
challenges = Challenge.where(shixun_id: shixuns.pluck(:id))
# 计算每个战队的成绩
current_competition.competition_teams.each do |team|
totoal_score = 0
total_time = 0
# 只有老师参赛则计算所有参赛队员的成绩,否则只计算学生的成绩
user_ids = only_teacher ? team.team_members.pluck(:user_id) : team.team_members.where(is_teacher: 0).pluck(:user_id)
stage_sections.each do |section|
shixun_identifiers = section.competition_entries.pluck(:shixun_identifier).reject(&:blank?)
shixun_ids = shixuns.select{ |shixun| shixun_identifiers.include?(shixun.identifier) }.map(&:id)
section_challenges = challenges.select{ |challenge| shixun_ids.include?(challenge.shixun_id) }
if section.score_source == 0
result1 = chart_stage_score user_ids, section.start_time, section.end_time, section_challenges, section_challenges.length/shixun_ids.length
else
result1 = chart_stage_rate user_ids, section.start_time, section.end_time, section_challenges, section_challenges.length/shixun_ids.length
end
score = result1[0]
time = result1[1]
totoal_score += score
total_time += time
end
# 比赛已截止且未有分数纪录 则创建
unless team.competition_scores.exists?(competition_id: current_competition.id, competition_stage_id: current_stage.id)
CompetitionScore.create!(user_id: team.user_id, competition_team_id: team.id, competition_id: current_competition.id,
competition_stage_id: current_stage.id, score: totoal_score, cost_time: total_time)
end
end
# 如果计算的是最后一个阶段,则同时计算总成绩(只有一个阶段则不需计算)
if current_stage.max_end_time == current_competition.max_stage_end_time && current_competition.competition_stages.size != 0
calculate_total_score current_competition
end
end
rescue Exception => e
uid_logger_error(e.message)
@message = "#{e.message}"
end
end
@message = "计算成功"
else
@message = "#{current_stage.name}还未结束"
end
end
def send_message
if current_stage.max_end_time && current_stage.max_end_time > Time.now
User.where(id: TeamMember.where(competition_team_id: current_competition.competition_teams.pluck(:id)).pluck(:user_id).uniq).each do |user|
name = current_competition.name + "#{current_competition.sub_title}#{stage.name}"
begin
if user.phone.present?
Educoder::Sms.send(mobile: user.phone.to_s, code: '1', send_type:'competition_start', user_name:user.show_name,
name:name, result:section.start_time.strftime('%Y-%m-%d %H:%M:%S'))
end
rescue => e
logger_error(e)
render_error("发送验证码出错")
end
end
else
render_error("#{current_stage.name}已结束")
end
end
private
def current_competition
@_current_competition ||= Competition.find(params[:competition_id])
end
def current_stage
@_current_stage ||= CompetitionStage.find_by!(competition_id: params[:competition_id], id: params[:id])
end
def update_form_params
params.permit(:stage_name, :score_rate, stage: [:start_time, :end_time, :entry, :score_source, identifiers: []])
end
# challenges 每阶段的所有关卡
# s_time 阶段开始时间 e_time 阶段结束时间
# rate 关卡经验值与分数的比值
# challenge_count 每个实训的关卡数
# 对三个实训的所有关卡循环: 找到在比赛时间内通关的最低耗时
def chart_stage_score user_ids, s_time, e_time, challenges, challenge_count
total_score = 0
total_time = 0
length = challenge_count #每个实训的关卡数
for i in 1..length
score = 0
time = 0
challenges.select{|challenge| challenge.position == i}.each do |challenge|
Game.where(:challenge_id => challenge.id, :user_id => user_ids, :status => 2).select{|game| game.open_time >= s_time && game.end_time <= e_time }.each do |game|
game_score = challenge.score
cost_time = (game.end_time.to_i - s_time.to_i) > 0 ? (game.end_time.to_i - s_time.to_i) : 0
if score < game_score
score = game_score
time = cost_time
elsif score == game_score
time = cost_time > time ? time : cost_time
end
end
end
total_score += score
total_time += time
end
return [total_score, total_time]
end
def chart_stage_rate user_ids, s_time, e_time, challenges, challenge_count
# 第三阶段的得分和耗时
total_score = 0
total_time = 0
length = challenge_count #每个实训的关卡数
for i in 1..length
score3 = 0
time3 = 0
challenges.select{|challenge| challenge.position == i}.each do |challenge|
Game.where(:challenge_id => challenge.id, :user_id => user_ids, :status => 2).select{|game| game.open_time >= s_time && game.end_time <= e_time }.each do |game|
outputs = game.outputs.select{|output| !output.text_scor.nil? && output.created_at <= e_time }
if outputs.present?
outputs = outputs.sort { |a, b| b[:text_scor].to_f <=> a[:text_scor].to_f }
myshixun_score = outputs.first.text_scor.to_f
myshixun_time = outputs.first.created_at.to_i - s_time.to_i
if score3 < myshixun_score
score3 = myshixun_score
time3 = myshixun_time
elsif score3 == myshixun_score
time3 = myshixun_time > time3 ? time3 : myshixun_time
end
end
end
end
total_score += score3
total_time += time3
end
return [total_score, total_time]
end
def calculate_total_score competition
competition.competition_teams.each do |team|
total_score = 0
total_time = 0
competition.competition_stages.where("score_rate > 0").each do |stage|
stage_score = team.competition_scores.where(competition_stage_id: stage.id).take
total_score += stage_score.try(:score).to_f * stage.score_rate
total_time += stage_score.try(:cost_time).to_i
end
CompetitionScore.create!(user_id: team.user_id, competition_team_id: team.id, competition_id: competition.id,
competition_stage_id: 0, score: total_score, cost_time: total_time)
end
end
end

@ -0,0 +1,57 @@
class Admins::CompetitionsController < Admins::BaseController
before_action :find_competition, except: [:index]
def index
# params[:sort_by] = params[:sort_by].presence || 'created_at'
# params[:sort_direction] = params[:sort_direction].presence || 'desc'
@competitions = Competition.all.order("created_at desc")
@params_page = params[:page] || 1
@competitions = paginate @competitions
ids = @competitions.map(&:id)
@member_count_map = TeamMember.where(competition_id: ids).group(:competition_id).count
@competition_hot = ModuleSetting.exists?(module_type: "Competition", property: "hot")
respond_to do |format|
format.js
format.html
end
end
def create
name = params[:competition_name].to_s.strip
Competition.create!(name: name)
render_ok
end
def hot_setting
if params[:hot].to_i == 1 && !ModuleSetting.exists?(module_type: "Competition", property: "hot")
ModuleSetting.create!(module_type: "Competition", property: "hot")
elsif params[:hot].to_i == 0 && ModuleSetting.exists?(module_type: "Competition", property: "hot")
ModuleSetting.where(module_type: "Competition", property: "hot").destroy_all
end
render_ok
end
def publish
@competition.update_attributes!(published_at: Time.now)
end
def unpublish
@competition.update_attributes!(published_at: nil)
end
def online_switch
if @competition.status
@competition.update_attributes!(status: false)
else
@competition.update_attributes!(status: true, online_time: Time.now)
end
end
private
def find_competition
@competition = Competition.find_by(id: params[:id])
end
end

@ -0,0 +1,37 @@
class Admins::EnrollListsController < Admins::BaseController
def index
@competition = current_competition
default_sort('created_at', 'desc')
enroll_lists = Admins::CompetitionEnrollListQuery.call(@competition, params)
@params_page = params[:page] || 1
@enroll_lists = paginate enroll_lists.preload(competition_team: [:user, :teachers], user: { user_extension: :school })
@personal = @competition.personal?
respond_to do |format|
format.js
format.html
format.xls{
filename = "#{@competition.name}竞赛报名列表_#{Time.now.strftime('%Y-%m-%d %H:%M:%S')}.xls"
send_data(shixun_list_xls(shixuns), :type => 'application/octet-stream', :filename => filename_for_content_disposition(filename))
}
end
end
def export
default_sort('created_at', 'desc')
@enroll_lists = Admins::CompetitionEnrollListQuery.call(current_competition, params)
@enroll_lists = @enroll_lists.preload(competition_team: [:user, :teachers], user: { user_extension: :school })
@competition_scores = current_competition.competition_scores.where(competition_stage_id: 0).order("score desc, cost_time desc").pluck(:competition_team_id)
@personal = current_competition.personal?
filename = ["#{current_competition.name}竞赛报名列表", Time.zone.now.strftime('%Y-%m-%d%H:%M:%S')].join('-') << '.xlsx'
render xlsx: 'export', filename: filename
end
private
def current_competition
@_current_competition ||= Competition.find(params[:competition_id])
end
end

@ -6,7 +6,12 @@ class Admins::FilesController < Admins::BaseController
Util.write_file(@file, file_path)
render_ok(source_id: params[:source_id], source_type: params[:source_type].to_s, url: file_url + "?t=#{Random.rand}")
render_ok(
source_id: params[:source_id],
source_type: params[:source_type].to_s,
suffix: params[:suffix].presence,
url: file_url
)
rescue StandardError => ex
logger_error(ex)
render_error('上传失败')
@ -33,14 +38,14 @@ class Admins::FilesController < Admins::BaseController
@_file_path ||= begin
case params[:source_type].to_s
when 'Shixun' then
Util::FileManage.disk_filename('Shixun', params[:source_id])
Util::FileManage.disk_filename('Shixun', params[:source_id], params[:suffix].presence)
else
Util::FileManage.disk_filename(params[:source_type].to_s, params[:source_id].to_s)
Util::FileManage.disk_filename(params[:source_type].to_s, params[:source_id].to_s, params[:suffix].presence)
end
end
end
def file_url
Util::FileManage.disk_file_url(params[:source_type].to_s, params[:source_id].to_s)
Util::FileManage.disk_file_url(params[:source_type].to_s, params[:source_id].to_s, params[:suffix].presence)
end
end

@ -26,7 +26,7 @@ class Admins::ShixunAuthorizationsController < Admins::BaseController
@applies = paginate applies.includes(user: :user_extension)
shixun_ids = @applies.map(&:container_id)
@shixun_map = Shixun.where(id: shixun_ids).each_with_object({}) { |s, h| h[s.id] = s }
@shixun_map = Shixun.where(id: shixun_ids).includes(:shixun_reviews).each_with_object({}) { |s, h| h[s.id] = s }
end
def agree

@ -46,7 +46,7 @@ class Admins::ShixunSettingsController < Admins::BaseController
tag_ids.each do |id|
unless tag_repertoire_ids.include?(id)
tag_repertoire = @shixun.shixun_tag_repertoires.new(shixun_id:@shixun.id,tag_repertoire_id:id)
tag_repertoire.save
tag_repertoire.save!
end
end
else

@ -0,0 +1,71 @@
class Admins::SubjectsController < Admins::BaseController
def index
default_sort('created_at', 'desc')
subjects = Admins::SubjectQuery.call(params)
@subjects = paginate subjects.includes(:repertoire, :subject_level_system, user: { user_extension: :school })
end
def edit
@subject = current_subject
end
def update
current_subject.update!(update_params)
flash[:success] = '保存成功'
redirect_to admins_subjects_path
end
def destroy
current_subject.destroy!
render_delete_success
end
# 隐藏
def hide
current_subject.update!(hidden: true)
render_ok
end
# 展示
def cancel_hide
current_subject.update!(hidden: false)
render_ok
end
# 设为主页展示
def homepage_show
current_subject.update!(homepage_show: true)
render_ok
end
# 取消主页展示
def cancel_homepage_show
current_subject.update!(homepage_show: false)
render_ok
end
# 设为金课
def excellent
current_subject.update!(excellent: true)
render_ok
end
# 取消金课
def cancel_excellent
current_subject.update!(excellent: false)
render_ok
end
private
def current_subject
@_current_subject ||= Subject.find(params[:id])
end
def update_params
params.require(:subject).permit(:repertoire_id, :subject_level_system_id, :student_count)
end
end

@ -0,0 +1,19 @@
class Admins::UserStatisticsController < Admins::BaseController
def index
default_sort('finish_shixun_count', 'desc')
total_count, users = Admins::UserStatisticQuery.call(params)
@users = paginate users, total_count: total_count
end
def export
default_sort('finish_shixun_count', 'desc')
params[:per_page] = 10000
_count, @users = Admins::UserStatisticQuery.call(params)
filename = ['用户实训情况', Time.zone.now.strftime('%Y%m%d%H%M%S')].join('-') << '.xlsx'
render xlsx: 'export', filename: filename
end
end

@ -32,13 +32,13 @@ class Admins::UsersController < Admins::BaseController
end
def lock
User.find(params[:user_id]).lock!
User.find(params[:id]).lock!
render_ok
end
def unlock
User.find(params[:user_id]).activate!
User.find(params[:id]).activate!
render_ok
end
@ -52,6 +52,12 @@ class Admins::UsersController < Admins::BaseController
render_ok(grade: user.grade)
end
def reset_login_times
User.find(params[:id]).reset_login_times!
render_ok
end
private
def update_params

@ -0,0 +1,79 @@
class Admins::WeappAdvertsController < Admins::BaseController
before_action :convert_file!, only: [:create]
def index
@adverts = WeappSettings::Advert.all
end
def create
position = WeappSettings::Advert.count + 1
ActiveRecord::Base.transaction do
advert = WeappSettings::Advert.create!(create_params.merge(position: position))
file_path = Util::FileManage.source_disk_filename(advert)
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
Util.write_file(@file, file_path)
end
flash[:success] = '保存成功'
redirect_to admins_weapp_adverts_path
end
def update
current_advert.update!(update_params)
render_ok
end
def destroy
ActiveRecord::Base.transaction do
current_advert.destroy!
# 前移
WeappSettings::Advert.where('position > ?', current_advert.position)
.update_all('position = position - 1')
file_path = Util::FileManage.source_disk_filename(current_advert)
File.delete(file_path) if File.exist?(file_path)
end
render_delete_success
end
def drag
move = WeappSettings::Advert.find_by(id: params[:move_id])
after = WeappSettings::Advert.find_by(id: params[:after_id])
Admins::DragWeappAdvertService.call(move, after)
render_ok
rescue ApplicationService::Error => e
render_error(e.message)
end
private
def current_advert
@_current_advert ||= WeappSettings::Advert.find(params[:id])
end
def create_params
params.require(:weapp_settings_advert).permit(:link)
end
def update_params
params.permit(:link, :online)
end
def convert_file!
max_size = 10 * 1024 * 1024 # 10M
file = params.dig('weapp_settings_advert', 'image')
if file.class == ActionDispatch::Http::UploadedFile
@file = file
render_error('请上传文件') if @file.size.zero?
render_error('文件大小超过限制') if @file.size > max_size
else
file = file.to_s.strip
return render_error('请上传正确的图片') if file.blank?
@file = Util.convert_base64_image(file, max_size: max_size)
end
rescue Base64ImageConverter::Error => ex
render_error(ex.message)
end
end

@ -0,0 +1,80 @@
class Admins::WeappCarouselsController < Admins::BaseController
before_action :convert_file!, only: [:create]
def index
@carousels = WeappSettings::Carousel.all
end
def create
position = WeappSettings::Carousel.count + 1
ActiveRecord::Base.transaction do
carousel = WeappSettings::Carousel.create!(create_params.merge(position: position))
file_path = Util::FileManage.source_disk_filename(carousel)
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
Util.write_file(@file, file_path)
end
flash[:success] = '保存成功'
redirect_to admins_weapp_carousels_path
end
def update
current_carousel.update!(update_params)
render_ok
end
def destroy
ActiveRecord::Base.transaction do
current_carousel.destroy!
# 前移
WeappSettings::Carousel.where('position > ?', current_carousel.position)
.update_all('position = position - 1')
file_path = Util::FileManage.source_disk_filename(current_carousel)
File.delete(file_path) if File.exist?(file_path)
end
render_delete_success
end
def drag
move = WeappSettings::Carousel.find_by(id: params[:move_id])
after = WeappSettings::Carousel.find_by(id: params[:after_id])
Admins::DragWeappCarouselService.call(move, after)
render_ok
rescue ApplicationService::Error => e
render_error(e.message)
end
private
def current_carousel
@_current_carousel ||= WeappSettings::Carousel.find(params[:id])
end
def create_params
params.require(:weapp_settings_carousel).permit(:link)
end
def update_params
params.permit(:link, :online)
end
def convert_file!
max_size = 10 * 1024 * 1024 # 10M
file = params.dig('weapp_settings_carousel', 'image')
if file.class == ActionDispatch::Http::UploadedFile
@file = file
render_error('请上传文件') if @file.size.zero?
render_error('文件大小超过限制') if @file.size > max_size
else
file = file.to_s.strip
return render_error('请上传正确的图片') if file.blank?
@file = Util.convert_base64_image(file, max_size: max_size)
end
rescue Base64ImageConverter::Error => ex
render_error(ex.message)
end
end

@ -86,8 +86,18 @@ class ApplicationController < ActionController::Base
when 8, 3, 5
# 邮箱类型的发送
sigle_para = {email: value}
# 60s内不能重复发送
send_email_limit_cache_key = "send_email_60_second_limit:#{value}"
tip_exception(-1, '请勿频繁操作') if Rails.cache.exist?(send_email_limit_cache_key)
# 短时间内不能大量发送
send_email_control = LimitForbidControl::SendEmailCode.new(value)
tip_exception(-1, '邮件发送太频繁,请稍后再试') if send_email_control.forbid?
begin
UserMailer.register_email(value, code).deliver_now
Rails.cache.write(send_email_limit_cache_key, 1, expires_in: 1.minute)
send_email_control.increment!
# Mailer.run.email_register(code, value)
rescue Exception => e
logger_error(e)
@ -112,6 +122,8 @@ class ApplicationController < ActionController::Base
"验证码发送次数超过频率"
when 43
"一天内同一手机号发送次数超过限制"
when 53
"手机号接收超过频率限制"
end
end
@ -338,9 +350,9 @@ class ApplicationController < ActionController::Base
# 如果代码窗口是隐藏的,则不用保存代码
return if myshixun.shixun.hide_code || myshixun.shixun.vnc
file_content = git_fle_content myshixun.repo_path, path
unless file_content.present?
raise("获取文件代码异常")
end
#unless file_content.present?
# raise("获取文件代码异常")
#end
logger.info("#######game_id:#{game_id}, file_content:#{file_content}")
game_code = GameCode.where(:game_id => game_id, :path => path).first
if game_code.nil?
@ -353,10 +365,10 @@ class ApplicationController < ActionController::Base
# Post请求
def uri_post(uri, params)
begin
uid_logger("--uri_exec: params is #{params}, url is #{uri}")
uid_logger_dubug("--uri_exec: params is #{params}, url is #{uri}")
uri = URI.parse(URI.encode(uri.strip))
res = Net::HTTP.post_form(uri, params).body
logger.info("--uri_exec: .....res is #{res}")
uid_logger_dubug("--uri_exec: .....res is #{res}")
JSON.parse(res)
rescue Exception => e
uid_logger_error("--uri_exec: exception #{e.message}")
@ -367,10 +379,10 @@ class ApplicationController < ActionController::Base
# 处理返回非0就报错的请求
def interface_post(uri, params, status, message)
begin
uid_logger("--uri_exec: params is #{params}, url is #{uri}")
uid_logger_dubug("--uri_exec: params is #{params}, url is #{uri}")
uri = URI.parse(URI.encode(uri.strip))
res = Net::HTTP.post_form(uri, params).body
logger.info("--uri_exec: .....res is #{res}")
uid_logger_dubug("--uri_exec: .....res is #{res}")
res = JSON.parse(res)
if (res && res['code'] != 0)
tip_exception(status, message)
@ -623,4 +635,13 @@ class ApplicationController < ActionController::Base
end
user
end
# 记录热门搜索关键字
def record_search_keyword
keyword = params[:keyword].to_s.strip
return if keyword.blank? || keyword.size <= 1
return unless HotSearchKeyword.available?
HotSearchKeyword.add(keyword)
end
end

@ -83,6 +83,7 @@ class AttachmentsController < ApplicationController
@file.destroy!
delete_file(@file_path)
normal_status("删除成功")
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)

@ -2,7 +2,7 @@ class BoardsController < ApplicationController
before_action :require_login, :check_auth
before_action :find_course, only: [:create]
before_action :set_board, except: [:create]
before_action :teacher_or_admin_allowed
before_action :teacher_allowed
def index
@boards = @course.boards.includes(messages: [:last_reply, :author])
@ -20,9 +20,8 @@ class BoardsController < ApplicationController
new_board.parent_id = board.try(:id)
new_board.position = board.children.count + 1
new_board.save!
render :json => {category_id: new_board.id, status: 0, message: "添加成功"}
end
normal_status(0, "添加成功")
end
# 子目录的拖动

@ -1,6 +1,72 @@
class Competitions::CompetitionTeamsController < Competitions::BaseController
before_action :tech_mode, only: [:shixun_detail, :course_detail]
def shixun_detail
start_time = current_competition.competition_mode_setting&.start_time || current_competition.start_time
end_time = current_competition.competition_mode_setting&.end_time || current_competition.end_time
team_user_ids = @team.team_members.pluck(:user_id)
shixuns = Shixun.where(user_id: team_user_ids, status: 2)
.where('shixuns.created_at > ? && shixuns.created_at <= ?', start_time, end_time)
shixuns = shixuns.joins('left join shixuns forked_shixuns on forked_shixuns.fork_from = shixuns.id and forked_shixuns.status = 2')
shixuns = shixuns.select('shixuns.id, shixuns.identifier, shixuns.user_id, shixuns.myshixuns_count, shixuns.name, shixuns.fork_from, sum(forked_shixuns.myshixuns_count) forked_myshixun_count')
@shixuns = shixuns.group('shixuns.id').order('shixuns.myshixuns_count desc').includes(:user)
shixun_ids = @shixuns.map(&:id)
@myshixun_count_map = get_valid_myshixun_count(shixun_ids)
@original_myshixun_count_map = @myshixun_count_map.clone
# forked shixun valid myshixun count
forked_shixun_map = Shixun.where(status: 2, fork_from: shixun_ids).select('id, fork_from')
forked_shixun_map = forked_shixun_map.each_with_object({}) { |sx, obj| obj[sx.id] = sx.fork_from }
@forked_myshixun_count_map = get_valid_myshixun_count(forked_shixun_map.keys)
@forked_myshixun_count_map.each { |k, v| @myshixun_count_map[forked_shixun_map[k]] += v }
@course_count_map = get_valid_course_count(shixun_ids)
@forked_map = get_valid_course_count(forked_shixun_map.keys)
@forked_course_count_map = {}
@forked_map.each do |forked_id, course_count|
@forked_course_count_map[forked_shixun_map[forked_id]] ||= 0
@forked_course_count_map[forked_shixun_map[forked_id]] += course_count
end
@forked_shixun_map = forked_shixun_map
end
def course_detail
start_time = current_competition.competition_mode_setting&.start_time || current_competition.start_time
end_time = current_competition.competition_mode_setting&.end_time || current_competition.end_time
@team_user_ids = @team.team_members.pluck(:user_id)
student_count_subquery = CourseMember.where('course_id = courses.id AND role = 4').select('count(*)').to_sql
subquery = StudentWork.where('homework_common_id = hcs.id')
.select('sum(compelete_status !=0 ) as finish, count(*) as total')
.having('total != 0 and finish >= (total / 2)').to_sql
course_ids = Course.where('courses.created_at > ?', start_time)
.where('courses.created_at <= ?', end_time)
.where("(#{student_count_subquery}) >= 3")
.where("exists(select 1 from homework_commons hcs where hcs.course_id = courses.id and hcs.publish_time is not null and hcs.publish_time < NOW() and hcs.homework_type = 4 and exists(#{subquery}))")
.joins('join course_members on course_members.course_id = courses.id and course_members.role in (1,2,3)')
.where(course_members: { user_id: @team_user_ids }).pluck(:id)
courses = Course.where(id: course_ids).joins(:practice_homeworks).where('homework_commons.publish_time < now()')
@courses = courses.select('courses.id, courses.name, courses.members_count, count(*) shixun_homework_count')
.group('courses.id').order('shixun_homework_count desc').having('shixun_homework_count > 0')
course_ids = @courses.map(&:id)
@course_myshixun_map = Myshixun.joins(student_works: :homework_common)
.where(homework_commons: { course_id: course_ids })
.where('exists(select 1 from games where games.myshixun_id = myshixuns.id and games.status = 2)')
.group('homework_commons.course_id').count
@course_shixun_count_map = get_valid_shixun_count(course_ids)
end
def index
admin_or_business? ? all_competition_teams : user_competition_teams
@competition = current_competition
@personal = current_competition.personal?
if admin_or_business?
all_competition_teams
user_competition_teams
else
user_competition_teams
end
end
def create
@ -12,7 +78,7 @@ class Competitions::CompetitionTeamsController < Competitions::BaseController
end
render_ok
rescue Competitions::CreatePersonalTeamService::Error => ex
rescue ApplicationService::Error => ex
render_error(ex.message)
end
@ -50,11 +116,13 @@ class Competitions::CompetitionTeamsController < Competitions::BaseController
keyword = params[:keyword].to_s.strip
if keyword.present?
teams = teams.joins(users: { user_extension: :school }).where('schools.name LIKE ?', "%#{keyword}%")
teams = teams.joins(user: { user_extension: :school })
.where('competition_teams.name LIKE :keyword OR schools.name LIKE :keyword', keyword: "%#{keyword}%")
end
@count = teams.count
@teams = paginate(teams.includes(:user, users: { user_extension: :school }))
@all_count = teams.count
@all_teams = paginate(teams.includes(:user, users: { user_extension: :school }))
@all_member_count = teams.joins(:team_members).count
end
def user_competition_teams
@ -67,4 +135,37 @@ class Competitions::CompetitionTeamsController < Competitions::BaseController
def save_params
params.permit(:name, teacher_ids: [], member_ids: [])
end
def tech_mode
# render_not_found if current_competition.mode != 3
@team = current_competition.competition_teams.find_by!(id: params[:id])
end
def get_valid_myshixun_count(ids)
Myshixun.where(shixun_id: ids)
.where('exists(select 1 from games where games.myshixun_id = myshixuns.id and games.status = 2)')
.group('shixun_id').count
end
def get_valid_course_count(ids)
percentage_sql = StudentWork.where('homework_common_id = homework_commons.id and homework_commons.publish_time is not null and homework_commons.publish_time < NOW()')
.select('sum(compelete_status !=0 ) as finish, count(*) as total')
.having('total != 0 and finish >= (total / 2)').to_sql
Course.joins(practice_homeworks: :homework_commons_shixun)
.where('shixun_id in (?)', ids)
.where("exists (#{percentage_sql})")
.group('shixun_id').count
end
def get_valid_shixun_count(ids)
percentage_sql = StudentWork.where('homework_common_id = homework_commons.id and homework_commons.publish_time is not null and homework_commons.publish_time < NOW()')
.select('sum(compelete_status !=0 ) as finish, count(*) as total')
.having('total != 0 and finish >= (total / 2)').to_sql
Shixun.joins(homework_commons_shixuns: :homework_common)
.where(homework_commons: { homework_type: 4 })
.where('course_id in (?)', ids)
.where("exists (#{percentage_sql})")
.group('course_id').count
end
end

@ -1,5 +1,11 @@
class Competitions::CompetitionsController < Competitions::BaseController
include CompetitionsHelper
skip_before_action :require_login
before_action :allow_visit, except: [:index]
before_action :require_admin, only: [:update, :update_inform]
before_action :chart_visible, only: [:charts, :chart_rules]
before_action :check_manager_permission!, only: [:update_md_content]
def index
# 已上架 或者 即将上架
@ -8,7 +14,7 @@ class Competitions::CompetitionsController < Competitions::BaseController
competitions =
case params[:category]
when 'nearly_published' then competitions.where(status: false)
when 'progressing' then competitions.where('end_time > NOW()')
when 'progressing' then competitions.where('status = 1 and end_time > NOW()')
when 'ended' then competitions.where('end_time < NOW()')
else competitions
end
@ -16,7 +22,7 @@ class Competitions::CompetitionsController < Competitions::BaseController
@count = competitions.count
competitions = competitions.order(published_at: :desc, online_time: :desc)
@competitions = paginate(competitions.includes(current_stage_section: :competition_stage))
@competitions = paginate(competitions.includes(sponsor_schools: :school, current_stage_section: :competition_stage))
ids = @competitions.map(&:id)
@member_count_map = TeamMember.where(competition_id: ids).group(:competition_id).count
@ -24,9 +30,111 @@ class Competitions::CompetitionsController < Competitions::BaseController
end
def show
unless current_competition.published? || admin_or_business?
render_forbidden
return
@competition = current_competition
current_competition.increment!(:visits)
end
def update
@competition.update_attributes!(introduction: params[:introduction])
Attachment.associate_container(params[:attachment_ids], @competition.id, @competition.class) if params[:attachment_ids]
normal_status("更新成功")
end
def common_header
@competition = current_competition
@competition_modules = @competition.unhidden_competition_modules
@user = current_user
current_competition.increment!(:visits)
end
def informs
status = params[:status] || 1
@informs = current_competition.informs.where(status: status)
end
def update_inform
tip_exception("标题和内容不能为空") if params[:name].blank? || params[:description].blank?
tip_exception("status参数有误") if params[:status].blank? || ![1, 2].include?(params[:status].to_i)
ActiveRecord::Base.transaction do
if params[:inform_id]
inform = current_competition.informs.find_by!(id: params[:inform_id])
inform.update_attributes!(name: params[:name], description: params[:description])
else
inform = current_competition.informs.create!(name: params[:name], description: params[:description], status: params[:status])
end
Attachment.associate_container(params[:attachment_ids], inform.id, inform.class) if params[:attachment_ids]
normal_status("更新成功")
end
end
def md_content
@md_content = CompetitionModuleMdContent.find_by!(id: params[:md_content_id])
end
def update_md_content
tip_exception("内容不能为空") if params[:content].blank?
tip_exception("缺少competition_module_id") if params[:competition_module_id].blank?
ActiveRecord::Base.transaction do
com_module = current_competition.competition_modules.find_by!(id: params[:competition_module_id])
if params[:md_content_id]
md_content = CompetitionModuleMdContent.find_by!(id: params[:md_content_id], competition_module_id: com_module.id)
md_content.update_attributes!(name: params[:name], content: params[:content])
else
stage = current_competition.competition_stages.find_by(id: params[:stage_id]) if params[:stage_id]
md_content = CompetitionModuleMdContent.create!(name: params[:name], content: params[:content], competition_module_id: com_module.id, competition_stage_id: stage&.id.to_i)
end
Attachment.associate_container(params[:attachment_ids], md_content.id, md_content.class) if params[:attachment_ids]
normal_status("更新成功")
end
end
def chart_rules
@competition = current_competition
com_module = @competition.competition_modules.find_by(module_type: "chart")
@rule_contents = com_module&.competition_module_md_contents
end
def update_chart_rules
tip_exception("内容不能为空") if params[:content].blank?
@competition = current_competition
@stage = @competition.competition_stages.find_by!(id: params[:stage_id]) if params[:stage_id]
chart_rule = @competition.chart_rules.where(competition_stage_id: @stage&.id).first
if chart_rule
chart_rule.update_attributes!(content: params[:content])
else
@competition.chart_rules.create!(competition_stage_id: @stage&.id, content: params[:content])
end
normal_status("更新成功")
end
def charts
@competition = current_competition
if params[:stage_id]
@stage = @competition.competition_stages.find_by(id: params[:stage_id])
end
@records = @competition.competition_teams.joins(:competition_scores).where(competition_scores: {competition_stage_id: @stage&.id.to_i})
.select("competition_teams.*, score, cost_time").order("score desc, cost_time desc")
current_team_ids = @competition.team_members.where(user_id: current_user.id).pluck(:competition_team_id).uniq
@user_ranks = @records.select{|com_team| current_team_ids.include?(com_team.id)}
@records = @records.where("score > 0")
@record_ids = @records.pluck(:id)
if params[:format] == "xlsx"
@records = @records.includes(user: [user_extension: :school], team_members: :user)
respond_to do |format|
format.xlsx{
set_export_cookies
chart_to_xlsx(@records, @competition)
exercise_export_name = "#{@competition.name}比赛成绩"
render xlsx: "#{exercise_export_name.strip}",template: "competitions/competitions/chart_list.xlsx.axlsx",locals:
{table_columns: @competition_head_cells, chart_lists: @competition_cells_column}
}
end
else
@records = @records.includes(:team_members, user: :user_extension).limit(@competition.awards_count)
end
end
@ -35,4 +143,72 @@ class Competitions::CompetitionsController < Competitions::BaseController
def current_competition
@_current_competition ||= Competition.find_by!(identifier: params[:id])
end
def allow_visit
return if current_competition.published? || admin_or_business?
return if current_competition.nearly_published? && current_competition.manager?(current_user)
render_forbidden
end
def chart_visible
chart_module = current_competition.competition_modules.find_by(name: "排行榜")
render_forbidden unless (chart_module.present? && !chart_module.hidden) || admin_or_business?
end
def check_manager_permission!
return if current_user.admin_or_business?
return if current_competition.nearly_published? && current_competition.manager?(current_user)
render_forbidden
end
# 竞赛成绩导出
def chart_to_xlsx records, competition
@competition_head_cells = []
@competition_cells_column = []
personal = competition.personal?
if personal # 个人赛
@competition_head_cells = %w(序号 姓名 学校 学号)
else # 战队赛
@competition_head_cells = %w(序号 战队名 指导老师 队员 学校)
end
statistic_stages = competition.competition_stages.where("score_rate > 0")
statistic_stages.each do |stage|
@competition_head_cells += ["#{stage.name}得分", "#{stage.name}用时"]
end
@competition_head_cells += ["总得分", "总用时"] if statistic_stages.size > 1
competition_scores = competition.competition_scores
records.each_with_index do |record, index|
row_cells_column = []
row_cells_column << index + 1
record_user = record.user
if personal
row_cells_column << record_user.real_name
row_cells_column << record_user.school_name
row_cells_column << record_user.student_id.present? ? (record_user.student_id.to_s + "\t") : "--"
else
row_cells_column << record.name
row_cells_column << record.teachers_name
row_cells_column << record.members_name
row_cells_column << chart_school_str(record.team_members.select{|member| !member.is_teacher}.pluck(:user_id))
end
statistic_stages.each do |stage|
stage_score = competition_scores.select{|score| score.competition_stage_id == stage.id && score.competition_team_id == record.id}.first
row_cells_column << stage_score&.score.to_f.round(2)
row_cells_column << com_spend_time(stage_score&.cost_time.to_i)
end
if statistic_stages.size > 1
row_cells_column << record&.score.to_f.round(2)
row_cells_column << com_spend_time(record&.cost_time.to_i)
end
@competition_cells_column.push(row_cells_column)
end
end
end

@ -7,6 +7,7 @@ class Competitions::StudentsController < Competitions::BaseController
end
students = User.joins(:user_extension).where(status: 1, user_extensions: { identity: 1 })
students = students.where(user_extensions: { school_id: current_competition.region_schools.pluck(:school_id) }) if current_competition.region_schools.size > 0
students = students.where.not(id: params[:student_ids]) if params[:student_ids].present?
students = students.where('LOWER(CONCAT(lastname, firstname, login, nickname)) LIKE ?', "%#{keyword}%")
@students = students.includes(user_extension: :school).limit(20)

@ -7,6 +7,7 @@ class Competitions::TeachersController < Competitions::BaseController
end
teachers = User.joins(:user_extension).where(status: 1, user_extensions: { identity: 0 })
teachers = teachers.where(user_extensions: { school_id: current_competition.region_schools.pluck(:school_id) }) if current_competition.region_schools.size > 0
teachers = teachers.where.not(id: params[:teacher_ids]) if params[:teacher_ids].present?
teachers = teachers.where('LOWER(CONCAT(lastname, firstname, login, nickname)) LIKE ?', "%#{keyword}%")
@teachers = teachers.includes(user_extension: :school).limit(10)

@ -1,39 +1,7 @@
module Admins::RenderHelper
extend ActiveSupport::Concern
def render_by_format(hash)
format = request.format.symbol
hash.key?(format) ? hash[format].call : hash[:html].call
end
def render_forbidden
render_by_format(html: -> { current_user&.business? ? render('admins/shared/403') : redirect_to('/403') },
json: -> { render status: 403, json: { messages: I18n.t('error.forbidden') } } )
end
def render_not_found
render_by_format(html: -> { render 'admins/shared/404' },
js: -> { render_js_error('资源未找到') },
json: -> { render status: 404, json: { message: '资源未找到' } })
end
def render_unprocessable_entity(message, type: :alert)
render_by_format(html: -> { render 'admins/shared/422' },
js: -> { render_js_error(message, type: type) },
json: -> { render status: 422, json: { message: message } })
end
alias_method :render_error, :render_unprocessable_entity
def internal_server_error(message = '系统错误')
@message = message
render_by_format(html: -> { render 'admins/shared/500' },
js: -> { render_js_error(message) },
json: -> { render status: 500, json: { message: message } })
end
def render_js_template(template, **opts)
render({ template: template, formats: :js }.merge(opts))
end
include Base::RenderHelper
def render_delete_success
render_js_template 'admins/shared/delete'

@ -1,4 +1,4 @@
module Admins::ErrorRescueHandler
module Base::ErrorRescueHandler
extend ActiveSupport::Concern
included do

@ -1,6 +1,11 @@
module Admins::PaginateHelper
module Base::PaginateHelper
extend ActiveSupport::Concern
def default_sort(sort_by, direction)
params[:sort_by] = params[:sort_by].presence || sort_by
params[:sort_direction] = params[:sort_direction].presence || direction
end
def offset
(page - 1) * per_page
end

@ -0,0 +1,37 @@
module Base::RenderHelper
extend ActiveSupport::Concern
def render_by_format(hash)
format = request.format.symbol
hash.key?(format) ? hash[format].call : hash[:html].call
end
def render_forbidden
render_by_format(html: -> { current_user&.business? ? render('shared/403') : redirect_to('/403') },
json: -> { render status: 403, json: { messages: I18n.t('error.forbidden') } } )
end
def render_not_found
render_by_format(html: -> { render 'shared/404' },
js: -> { render_js_error('资源未找到') },
json: -> { render status: 404, json: { message: '资源未找到' } })
end
def render_unprocessable_entity(message, type: :alert)
render_by_format(html: -> { render 'shared/422' },
js: -> { render_js_error(message, type: type) },
json: -> { render status: 422, json: { message: message } })
end
alias_method :render_error, :render_unprocessable_entity
def internal_server_error(message = '系统错误')
@message = message
render_by_format(html: -> { render 'shared/500' },
js: -> { render_js_error(message) },
json: -> { render status: 500, json: { message: message } })
end
def render_js_template(template, **opts)
render({ template: template, formats: :js }.merge(opts))
end
end

@ -0,0 +1,16 @@
module Cooperative::RenderHelper
include Base::RenderHelper
def render_delete_success
render_js_template 'cooperative/shared/delete'
end
alias_method :render_success_js, :render_delete_success
def render_js_error(message, type: :alert)
if type == :notify
render js: "$.notify({ message: '#{message}' },{ type: 'danger', delay: 5000 });"
else
render_js_template 'cooperative/shared/error', locals: { message: message }
end
end
end

@ -2,6 +2,7 @@ module LaboratoryHelper
extend ActiveSupport::Concern
included do
helper_method :current_laboratory
helper_method :default_setting
end
@ -9,6 +10,10 @@ module LaboratoryHelper
@_current_laboratory ||= (Laboratory.find_by_subdomain(request.subdomain) || Laboratory.find(1))
end
def default_laboratory
@_default_laboratory ||= Laboratory.find(1)
end
def default_setting
@_default_setting ||= LaboratorySetting.find_by(laboratory_id: 1)
end

@ -9,6 +9,12 @@ module LoggerHelper
Rails.logger.info("##:#{current_user.try(:id)} --#{message}")
end
# debug日志
def uid_logger_dubug(message)
Rails.logger.info("##dubug-#{current_user.try(:id)} --#{message}")
end
# 以用户id开始的日志定义
def uid_logger_error(message)
Rails.logger.error("##:#{current_user.try(:id)} --#{message}")

@ -0,0 +1,58 @@
class Cooperative::BaseController < ApplicationController
include Base::PaginateHelper
include Cooperative::RenderHelper
include Base::ErrorRescueHandler
layout 'cooperative'
skip_before_action :verify_authenticity_token
before_action :laboratory_exist!, :require_login, :require_cooperative_manager!
after_action :rebind_event_if_ajax_render_partial
helper_method :current_laboratory, :current_setting_or_default
private
def current_laboratory
@_current_laboratory ||= Laboratory.find_by_subdomain(request.subdomain)
end
def current_setting_or_default(name)
current_laboratory.public_send(name) || default_setting.public_send(name)
end
def laboratory_exist!
return if current_laboratory.present?
redirect_to '/403'
end
def require_login
return if User.current.logged?
redirect_to "/login?back_url=#{CGI::escape(request.fullpath)}"
end
def require_cooperative_manager!
return if current_user.blank? || !current_user.logged?
return if current_user.admin_or_business?
return if current_laboratory.laboratory_users.exists?(user_id: current_user.id)
render_forbidden
end
# 触发after ajax render partial hooks执行一些因为局部刷新后失效的绑定事件
def rebind_event_if_ajax_render_partial
return if request.format.symbol != :js
return if response.content_type != 'text/javascript'
path = Rails.root.join('app/views/cooperative/shared/after_render_js_hook.js.erb')
return unless File.exists?(path)
append_js = ERB.new(File.open(path).read).result
response.body += append_js
end
end

@ -0,0 +1,80 @@
class Cooperative::CarouselsController < Cooperative::BaseController
before_action :convert_file!, only: [:create]
def index
@images = current_laboratory.portal_images.order(position: :asc)
end
def create
position = current_laboratory.portal_images.count + 1
ActiveRecord::Base.transaction do
image = current_laboratory.portal_images.create!(create_params.merge(position: position))
file_path = Util::FileManage.disk_filename('PortalImage', image.id)
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
Util.write_file(@file, file_path)
end
flash[:success] = '保存成功'
redirect_to cooperative_carousels_path(current_laboratory)
end
def update
current_image.update!(update_params)
render_ok
end
def destroy
ActiveRecord::Base.transaction do
current_image.destroy!
# 前移
current_laboratory.portal_images.where('position > ?', current_image.position)
.update_all('position = position - 1')
file_path = Util::FileManage.disk_filename('PortalImage', current_image.id)
File.delete(file_path) if File.exist?(file_path)
end
render_delete_success
end
def drag
move = current_laboratory.portal_images.find_by(id: params[:move_id])
after = current_laboratory.portal_images.find_by(id: params[:after_id])
Admins::DragPortalImageService.call(current_laboratory, move, after)
render_ok
rescue Admins::DragPortalImageService::Error => e
render_error(e.message)
end
private
def current_image
@_current_image ||= current_laboratory.portal_images.find(params[:id])
end
def create_params
params.require(:portal_image).permit(:name, :link)
end
def update_params
params.permit(:name, :link, :status)
end
def convert_file!
max_size = 10 * 1024 * 1024 # 10M
file = params.dig('portal_image', 'image')
if file.class == ActionDispatch::Http::UploadedFile
@file = file
render_error('请上传文件') if @file.size.zero?
render_error('文件大小超过限制') if @file.size > max_size
else
file = file.to_s.strip
return render_error('请上传正确的图片') if file.blank?
@file = Util.convert_base64_image(file, max_size: max_size)
end
rescue Base64ImageConverter::Error => ex
render_error(ex.message)
end
end

@ -0,0 +1,4 @@
class Cooperative::DashboardsController < Cooperative::BaseController
def show
end
end

@ -0,0 +1,46 @@
class Cooperative::FilesController < Cooperative::BaseController
before_action :convert_file!, only: [:create]
def create
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
Util.write_file(@file, file_path)
render_ok(source_id: params[:source_id], source_type: params[:source_type].to_s, url: file_url + "?t=#{Random.rand}")
rescue StandardError => ex
logger_error(ex)
render_error('上传失败')
end
private
def convert_file!
max_size = 10 * 1024 * 1024 # 10M
if params[:file].class == ActionDispatch::Http::UploadedFile
@file = params[:file]
render_error('请上传文件') if @file.size.zero?
render_error('文件大小超过限制') if @file.size > max_size
else
file = params[:file].to_s.strip
return render_error('请上传正确的图片') if file.blank?
@file = Util.convert_base64_image(file, max_size: max_size)
end
rescue Base64ImageConverter::Error => ex
render_error(ex.message)
end
def file_path
@_file_path ||= begin
case params[:source_type].to_s
when 'Shixun' then
Util::FileManage.disk_filename('Shixun', params[:source_id])
else
Util::FileManage.disk_filename(params[:source_type].to_s, params[:source_id].to_s)
end
end
end
def file_url
Util::FileManage.disk_file_url(params[:source_type].to_s, params[:source_id].to_s)
end
end

@ -0,0 +1,14 @@
class Cooperative::LaboratorySettingsController < Cooperative::BaseController
def edit
@laboratory = current_laboratory
end
def update
Admins::SaveLaboratorySettingService.call(current_laboratory, form_params)
render_ok
end
def form_params
params.permit(:identifier, :name, :nav_logo, :login_logo, :tab_logo, :footer, navbar: %i[name link hidden])
end
end

@ -0,0 +1,24 @@
class Cooperative::LaboratoryUsersController < Cooperative::BaseController
def index
laboratory_users = current_laboratory.laboratory_users
@laboratory_users = paginate laboratory_users.includes(user: { user_extension: [:school, :department] })
end
def create
Admins::AddLaboratoryUserService.call(current_laboratory, params.permit(user_ids: []))
render_ok
end
def destroy
return render_error('不能删除自己', type: :notify) if current_laboratory_user.user_id == current_user.id
current_laboratory_user.destroy!
render_delete_success
end
private
def current_laboratory_user
@_current_laboratory_user ||= current_laboratory.laboratory_users.find(params[:id])
end
end

@ -0,0 +1,15 @@
class Cooperative::UsersController < Cooperative::BaseController
def index
params[:sort_by] = params[:sort_by].presence || 'created_on'
params[:sort_direction] = params[:sort_direction].presence || 'desc'
users = Admins::UserQuery.call(search_params)
@users = paginate users.includes(user_extension: :school)
end
private
def search_params
params.permit(:name, :sort_by, :sort_direction)
end
end

@ -2,15 +2,15 @@ class CourseGroupsController < ApplicationController
before_action :require_login, :check_auth
before_action :set_group, except: [:create]
before_action :find_course, only: [:create]
before_action :teacher_or_admin_allowed
before_action :teacher_allowed
def create
tip_exception("分班名称不能为空") if params[:name].blank?
if @course.course_groups.where(name: params[:name]).count > 0
normal_status(-1, "已存在同名分班")
else
@course.course_groups.create!(name: params[:name], position: @course.course_groups.count + 1)
normal_status(0, "创建成功")
course_group = @course.course_groups.create!(name: params[:name], position: @course.course_groups.count + 1)
render :json => {group_id: course_group.id, status: 0, message: "创建成功"}
end
end

@ -2,7 +2,8 @@ class CourseModulesController < ApplicationController
before_action :require_login, :check_auth
before_action :set_module, except: [:unhidden_modules]
before_action :find_course, only: [:unhidden_modules]
before_action :teacher_or_admin_allowed
before_action :teacher_or_admin_allowed, except: [:add_second_category]
before_action :teacher_allowed, only: [:add_second_category]
# 模块置顶
def sticky_module

@ -1,7 +1,7 @@
class CourseSecondCategoriesController < ApplicationController
before_action :require_login, :check_auth
before_action :set_category
before_action :teacher_or_admin_allowed
before_action :teacher_allowed
# 目录重命名
def rename_category

@ -28,7 +28,8 @@ class CoursesController < ApplicationController
:switch_to_teacher, :switch_to_assistant, :switch_to_student, :exit_course,
: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]
:delete_informs, :change_member_role, :course_groups, :join_course_group, :statistics,
:work_score, :act_score]
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,
@ -47,6 +48,7 @@ class CoursesController < ApplicationController
before_action :validate_page_size, only: :mine
before_action :course_tasks, only: [:tasks_list, :update_task_position]
before_action :validate_inform_params, only: [:update_informs, :new_informs]
before_action :course_member_allowed, only: [:statistics, :work_score, :act_score]
if RUBY_PLATFORM =~ /linux/
require 'simple_xlsx_reader'
@ -227,6 +229,39 @@ class CoursesController < ApplicationController
end
end
def statistics
max_exp = User.where(id: @course.students.pluck(:user_id)).pluck(:experience).max
limit = params[:limit] || 5
@top_scores = course_statistics(@course, max_exp, limit)
end
def work_score
sort = params[:sort] || "desc"
group_ids = params[:group_ids].is_a?(Array) ? params[:group_ids] : params[:group_ids].split(",") if params[:group_ids]
@course_members = course_work_scores @course, sort, group_ids
if @user_course_identity == Course::STUDENT
user_ids = @course_members.map(&:user_id)
@rank = user_ids.index(current_user.id).to_i + 1
@course_members = @course_members.select{|member| member.user_id == current_user.id}
else
@course_members = paginate @course_members
end
end
def act_score
group_ids = params[:group_ids].is_a?(Array) ? params[:group_ids] : params[:group_ids].split(",") if params[:group_ids]
all_members = course_act_scores @course, group_ids
@course_members = all_members[0 .. 9]
if @user_course_identity == Course::STUDENT
user_ids = all_members.map(&:user_id)
rank = user_ids.index(current_user.id).to_i + 1
if rank > 10
current_member = all_members.select{|member| member.user_id == current_user.id}
@course_members << current_member
end
end
end
def join_excellent_course
tip_exception("您已是课堂成员") if current_user.member_of_course?(@course)
tip_exception("请通过邀请码加入课堂") unless @course.excellent
@ -583,6 +618,8 @@ class CoursesController < ApplicationController
# 学生身份的处理
student_member = course_members.where(role: %i[STUDENT]).take
# 不存在则创建学生身份
if params[:roles].include?("STUDENT") && student_member.blank?
correspond_teacher_exist = CourseMember.exists?(user_id: params[:user_id], is_active: 1, course_id: @course.id, role: %i[CREATOR PROFESSOR ASSISTANT_PROFESSOR])
new_student = CourseMember.new(user_id: params[:user_id], course_id: @course.id, role: 4)
@ -597,6 +634,9 @@ class CoursesController < ApplicationController
student_member.destroy!
CourseDeleteStudentDeleteWorksJob.perform_later(@course.id, [params[:user_id]])
# CourseDeleteStudentNotifyJob.perform_later(@course.id, [params[:user_id]], current_user.id)
elsif params[:roles].include?("STUDENT") && student_member.present? && !params[:roles].include?("PROFESSOR") && !params[:roles].include?("ASSISTANT_PROFESSOR")
# 学生身份存在且学生没有教师身份时更新is_active
student_member.update_attributes!(is_active: 1)
end
normal_status(0, "修改成功")
@ -1374,6 +1414,10 @@ class CoursesController < ApplicationController
end
end
def course_member_allowed
tip_exception(403, "..") if @user_course_identity > Course::STUDENT
end
def course_tasks
case params[:container_type]
when 'shixun_homework'
@ -1461,319 +1505,93 @@ class CoursesController < ApplicationController
act_scores
end
def course_info_to_xlsx course
#课堂信息
@course_info = []
course_info_title = "课堂信息概要"
course_id = course.id
course_name = course.name
course_list_name = course.course_list.present? ? course.course_list.name : "--"
course_assistants = course.teachers
course_assistants_count = course_assistants&.size
course_assistants_name = course_assistants_count > 0 ? course_assistants.map{|m| m.user.real_name}.join('、') : "--"
course_teacher_member = course.course_members.course_user_role(%i[CREATOR])
course_teacher = course_teacher_member.present? ? course_teacher_member.first.user.real_name : "--"
course_class_counts = course.course_groups_count
course_students_count = course.students.size
course_1 = ["课堂编号",course_id]
course_2 = ["课程名称",course_list_name]
course_3 = ["课堂名称",course_name]
course_4 = ["教师团队(#{course_assistants_count})", course_assistants_name]
course_5 = ["主讲教师", course_teacher]
course_6 = ["分班",course_class_counts]
course_7 = ["学生", course_students_count]
course_main_info = [course_1,course_2,course_3,course_4,course_5,course_6,course_7]
course_group_info_head = %w(序号 分班名称 邀请码 学生数量)
course_group_info_body = []
none_group_counts = course.none_group_count
#当有未分班时,应该也做个统计
if none_group_counts > 0
none_group_index = 2
no_group_array = [1,"未分班",course.invite_code,none_group_counts]
course_group_info_body.push(no_group_array)
else
none_group_index = 1
end
if course.course_groups.exists?
course.course_groups.each_with_index do |group, index|
group_index = (index+none_group_index)
group_name = group.name
group_code = group.invite_code
group_count = group.course_members_count
group_array = [group_index,group_name,group_code,group_count]
course_group_info_body.push(group_array)
end
end
course_group_info = [course_group_info_head,course_group_info_body]
@course_info += [course_info_title,course_main_info,course_group_info]
end
def act_score_to_xlsx all_members
#课堂活跃度
@user_activity_level = []
course_user_level = []
course_activity_title = "课堂活跃度统计"
user_cell_head = %w(排名 真实姓名 登录名 邮箱 学号 学校 分班 作业完成数(*10) 试卷完成数(*10) 问卷完成数(*7) 资源发布数(*5) 帖子发布数(*2) 帖子回复数(*1) 作业回复数(*1) 活跃度)
all_members.each do |u|
#用户的基本信息
user = u.user
user_login = user.login
user_name = user.real_name
user_mail = user.mail
user_stu_id = u.student_id.present? ? (u.student_id.to_s + "\t") : "--"
user_school = user.school_name
user_course_group = u.course_group_name
#课堂活跃度统计
user_homeworks_num = u.homework_num.to_i #完成的作业数
user_graduate_num = u.graduation_num.to_i #毕业任务完成数
user_exercise_num = u.exercise_num.to_i #根据试卷的id来查找
user_poll_num = u.poll_num.to_i #已完成问卷
user_file_num = u.resource_num.to_i
user_messages_num = u.message_num.to_i #帖子发布数
user_reply_num = u.message_reply_num.to_i #帖子回复数
user_work_reply_num = u.homework_journal_num.to_i #作业回复数的数量
c_works_num = (user_homeworks_num + user_graduate_num)*10
c_exercise_num = user_exercise_num*10
c_poll_num = user_poll_num*7
c_file_num = user_file_num*5
c_message_num = user_messages_num*2
c_reply_num = user_reply_num
user_activity_levels = c_works_num + c_exercise_num + c_poll_num + c_file_num + c_message_num + c_reply_num + user_work_reply_num
user_ac_level = {
u_1: user_name,
u_2: user_login,
u_2_1: user_mail,
u_3: user_stu_id,
u_4: user_school,
u_5: user_course_group,
u_6: c_works_num,
u_7: c_exercise_num,
u_8: c_poll_num,
u_9: c_file_num,
u_10: c_message_num,
u_11: c_reply_num,
u_12: user_work_reply_num,
u_13: user_activity_levels
}
course_user_level.push(user_ac_level)
#.课堂活跃度统计的集合
course_user_level = course_user_level.sort_by {|k| k[:u_13]}.reverse
@user_activity_level = [course_activity_title,user_cell_head,course_user_level]
end
end
def member_to_xlsx(course,all_members,homeworks,exercises,tasks)
#课堂的作业信息
shixun_homeworks = homeworks.search_homework_type(4) #全部实训作业
shixun_titles = shixun_homeworks.pluck(:name) + ["总得分"]
# 更新实训作业成绩
unless course.is_end
shixun_homeworks.includes(:homework_challenge_settings, :published_settings, :homework_commons_shixun).each do |homework|
homework.update_homework_work_score
end
end
shixun_homeworks = shixun_homeworks&.includes(score_student_works: :user)
common_homeworks = homeworks.search_homework_type(1) #全部普通作业
common_titles = common_homeworks.pluck(:name)+ ["总得分"]
common_homeworks = common_homeworks&.includes(score_student_works: :user)
group_homeworks = homeworks.search_homework_type(3) #全部分组作业
group_titles = group_homeworks.pluck(:name)+ ["总得分"]
group_homeworks = group_homeworks&.includes(score_student_works: :user)
task_titles = tasks.pluck(:name) + ["总得分"]
tasks = tasks&.includes(user: :user_extension, score_graduation_works: :user)
exercise_titles = exercises.pluck(:exercise_name) + ["总得分"]
exercises = exercises&.includes(user: :user_extension, score_exercise_users: :user)
total_user_score_array = [] #学生总成绩集合
all_members.each do |u|
#用户的基本信息
user = u.user
user_login = user.login
user_name = user.real_name
user_mail = user.mail
user_stu_id = user.student_id.present? ? (user.student_id.to_s + "\t") : "--"
user_school = user.school_name
user_course_group = u.course_group_name
user_info_array = [user_name,user_login,user_mail,user_stu_id,user_school,user_course_group] #用户的信息集合
user_work_scores = []
#学生总成绩
shixun_score = 0.0 # 实训作业的总分
common_score = 0.0 #普通作业的总分
group_score = 0.0 #分组作业的总分
task_score = 0.0 # 毕业任务的总得分
exercise_score = 0.0 #试卷的总得分
shixun_score_array = []
common_score_array = []
group_score_array = []
task_score_array = []
exercise_score_array = []
#实训作业
if shixun_homeworks.size > 0
shixun_homeworks.each do |s|
user_student_work = s.score_student_works.select{|work| work.user_id == user.id}.first #当前用户的对该作业的回答
if user_student_work.nil?
h_score = 0.0 #该作业的得分为0
else
h_score = user_student_work.work_score.nil? ? 0.0 : user_student_work.work_score #用户对该作业的分数
end
shixun_score_array.push(h_score)
end
end
shixun_score += shixun_score_array.sum
shixun_score_array.push(shixun_score) #shixun_score_array的最后一行为总分
user_work_scores += user_info_array + shixun_score_array #单个用户的实训作业得分信息
#普通作业
if common_homeworks.size > 0
common_homeworks.each do |c|
user_student_work_1 = c.score_student_works.select{|work| work.user_id == user.id}.first #当前用户的对该作业的回答
if user_student_work_1.nil?
h_score_1 = 0.0 #该作业的得分为0
else
h_score_1 = user_student_work_1.work_score.nil? ? 0.0 : user_student_work_1.work_score #用户对该作业的分数
end
common_score_array.push(h_score_1)
end
end
common_score += common_score_array.sum
common_score_array.push(common_score) #shixun_score_array的最后一行为总分
user_work_scores += common_score_array #单个用户的普通作业得分信息
#分组作业
if group_homeworks.size > 0
group_homeworks.each do |g|
user_student_work_3 = g.score_student_works.select{|work| work.user_id == user.id}.first #当前用户的对该作业的回答
if user_student_work_3.nil?
h_score_3 = 0.0 #该作业的得分为0
else
h_score_3 = user_student_work_3.work_score.nil? ? 0.0 : user_student_work_3.work_score #用户对该作业的分数
end
group_score_array.push(h_score_3)
end
end
group_score += group_score_array.sum
group_score_array.push(group_score) #shixun_score_array的最后一行为总分
user_work_scores += group_score_array #单个用户的分组作业得分信息
#毕设作业
if tasks.size > 0
tasks.each do |task|
graduation_work = task.score_graduation_works.select{|work| work.user_id == user.id}.first
if graduation_work.nil?
t_score = 0.0
else
t_score = graduation_work.work_score.nil? ? 0.0 : graduation_work.work_score
end
task_score_array.push(t_score)
end
end
task_score += task_score_array.sum
task_score_array.push(task_score)
user_work_scores += task_score_array #单个用户的分组作业得分信息
#试卷
if exercises.size > 0
exercises.each do |ex|
exercise_work = ex.score_exercise_users.select{|work| work.user_id == user.id}.first
if exercise_work.nil?
e_score = 0.0
else
e_score = exercise_work.score.nil? ? 0.0 : exercise_work.score
end
exercise_score_array.push(e_score)
end
end
exercise_score += exercise_score_array.sum
exercise_score_array.push(exercise_score)
user_work_scores += exercise_score_array #单个用户的分组作业得分信息
total_user_scores = shixun_score + common_score + group_score + task_score + exercise_score
user_work_scores.push(total_user_scores) #个人的行内容添加总成绩
total_user_score_array.push(user_work_scores) # 全部成员的集合
end
#2.学生总成绩的集合
## 作业标题的集合
course_user_score_title = "学生总成绩"
score_title_cells = shixun_titles + common_titles + group_titles + task_titles + exercise_titles
score_title_counts = [shixun_titles.count,common_titles.count,group_titles.count,task_titles.count,exercise_titles.count]
score_cell_head = %w(序号 真实姓名 登录名 邮箱 学号 学校 分班) + score_title_cells + ["个人总成绩"]
@course_user_scores = [course_user_score_title,score_cell_head,score_title_counts,total_user_score_array]
#作业的全部集合
@shixun_work_arrays = []
@group_work_arrays = []
@common_work_arrays = []
@task_work_arrays = []
@exercise_work_arrays = []
count_1 = shixun_homeworks.size
count_2 = common_homeworks.size
count_3 = group_homeworks.size
count_4 = tasks.size
#实训作业
shixun_homeworks.each_with_index do |s,index|
all_student_works = s.score_student_works #该实训题的全部用户回答
title_no = index.to_i + 1
student_work_to_xlsx(all_student_works,s)
shixun_work_display_name = format_sheet_name (title_no.to_s + "." + s.name).strip.first(30)
shixun_work_content = [shixun_work_display_name,@work_head_cells,@work_cells_column]
@shixun_work_arrays.push(shixun_work_content)
end
#普通作业
common_homeworks.each_with_index do |c,index|
all_student_works = c.score_student_works #当前用户的对该作业的回答
title_no = count_1 + index.to_i + 1
student_work_to_xlsx(all_student_works,c)
work_name = format_sheet_name (title_no.to_s + "." + c.name).strip.first(30)
work_content = [work_name,@work_head_cells,@work_cells_column]
@common_work_arrays.push(work_content)
title_no
end
#分组作业
group_homeworks.each_with_index do |c,index|
all_student_works = c.score_student_works #当前用户的对该作业的回答
title_no = count_1 + count_2 + index.to_i + 1
student_work_to_xlsx(all_student_works,c)
work_name = format_sheet_name (title_no.to_s + "." + c.name).strip.first(30)
work_content = [work_name,@work_head_cells,@work_cells_column]
@group_work_arrays.push(work_content)
end
#毕设任务
tasks.each_with_index do |c,index|
all_student_works = c.score_graduation_works #当前用户的对该作业的回答
title_no = count_1 + count_2 + count_3 + index.to_i + 1
graduation_work_to_xlsx(all_student_works,c,current_user)
work_name = format_sheet_name (title_no.to_s + "." + c.name).strip.first(30)
work_content = [work_name,@head_cells_column,@task_cells_column]
@task_work_arrays.push(work_content)
end
#试卷的导出
exercises.each_with_index do |c,index|
all_student_works = c.score_exercise_users #当前用户的对该作业的回答
title_no = count_1 + count_2 + count_3 + count_4 + index.to_i + 1
get_export_users(c,course,all_student_works)
work_name = format_sheet_name (title_no.to_s + "." + c.exercise_name).strip.first(30)
work_content = [work_name,@table_columns,@user_columns]
@exercise_work_arrays.push(work_content)
end
def course_statistics course, max_exp, limit
max_rate = 20.0 / max_exp
sql_select = %Q{ SELECT a.*, (message_num*0.2 + message_reply_num*0.1 + resource_num*0.5 + homework_journal_num*0.1 + graduation_num +
homework_num + exercise_num + poll_num*0.7 + exercise_score * 0.7 + graduation_score * 0.7 + homework_score * 0.7 + exp*#{max_rate})
AS score from
(select cm.*, users.experience as exp,
(SELECT count(messages.id) FROM messages join boards on messages.board_id = boards.id WHERE boards.course_id = #{course.id}
AND messages.author_id = cm.user_id and messages.parent_id is null) AS message_num,
(SELECT count(messages.id) FROM messages join boards on messages.board_id = boards.id WHERE boards.course_id = #{course.id}
AND messages.author_id = cm.user_id and messages.parent_id is not null) AS message_reply_num,
(SELECT count(attachments.id) FROM attachments WHERE container_id = #{course.id} and container_type = "Course"
AND attachments.author_id = cm.user_id) AS resource_num,
(SELECT count(jfm.id) FROM journals_for_messages AS jfm, homework_commons hs WHERE jfm.jour_id = hs.id AND
jfm.user_id = cm.user_id and jfm.jour_type = "HomeworkCommon" and hs.course_id = #{course.id}) AS homework_journal_num,
(SELECT COUNT(gw.id) FROM graduation_works AS gw, graduation_tasks AS gt WHERE gw.graduation_task_id = gt.id AND
gt.course_id = #{course.id} AND gw.work_status != 0 AND gw.user_id = cm.user_id) AS graduation_num,
(SELECT IFNULL(sum(gw.work_score),0) FROM graduation_works AS gw, graduation_tasks AS gt WHERE gw.graduation_task_id = gt.id AND
gt.course_id = #{course.id} AND gw.work_status != 0 AND gw.user_id = cm.user_id) AS graduation_score,
(SELECT COUNT(ss.id) FROM student_works AS ss ,homework_commons AS hc WHERE ss.homework_common_id = hc.id AND
hc.course_id = #{course.id} AND ss.work_status != 0 AND ss.user_id = cm.user_id) AS homework_num,
(SELECT IFNULL(sum(ss.work_score),0) FROM student_works AS ss ,homework_commons AS hc WHERE ss.homework_common_id = hc.id AND
hc.course_id = #{course.id} AND ss.work_status != 0 AND ss.user_id = cm.user_id) AS homework_score,
(SELECT COUNT(eu.id) FROM exercise_users AS eu,exercises WHERE eu.exercise_id = exercises.id AND exercises.course_id = #{course.id}
AND eu.commit_status = 1 AND eu.user_id = cm.user_id) AS exercise_num,
(SELECT IFNULL(sum(eu.score),0) FROM exercise_users AS eu,exercises WHERE eu.exercise_id = exercises.id AND exercises.course_id = #{course.id}
AND eu.commit_status = 1 AND eu.user_id = cm.user_id) AS exercise_score,
(SELECT COUNT(pu.id) FROM poll_users AS pu, polls WHERE pu.poll_id = polls.id AND polls.course_id = #{course.id}
AND pu.commit_status = 1 AND pu.user_id = cm.user_id) AS poll_num
FROM course_members cm join users on cm.user_id = users.id
WHERE cm.role = 4 and cm.course_id = #{course.id}) a ORDER BY score desc limit #{limit};
}
CourseMember.find_by_sql(sql_select)
end
def course_work_scores course, sort, group_ids
sql_select = %Q{ SELECT a.*,
(exercise_score + graduation_score + common_score + practice_score + group_score) AS score from
(select cm.*,
(SELECT IFNULL(sum(gw.work_score),0) FROM graduation_works AS gw, graduation_tasks AS gt WHERE gw.graduation_task_id = gt.id AND
gt.course_id = #{course.id} AND gw.work_status != 0 AND gw.user_id = cm.user_id) AS graduation_score,
(SELECT IFNULL(sum(ss.work_score),0) FROM student_works AS ss ,homework_commons AS hc WHERE ss.homework_common_id = hc.id AND
hc.course_id = #{course.id} AND ss.work_status != 0 AND ss.user_id = cm.user_id and hc.homework_type=1) AS common_score,
(SELECT IFNULL(sum(ss.work_score),0) FROM student_works AS ss ,homework_commons AS hc WHERE ss.homework_common_id = hc.id AND
hc.course_id = #{course.id} AND ss.work_status != 0 AND ss.user_id = cm.user_id and hc.homework_type=3) AS group_score,
(SELECT IFNULL(sum(ss.work_score),0) FROM student_works AS ss ,homework_commons AS hc WHERE ss.homework_common_id = hc.id AND
hc.course_id = #{course.id} AND ss.work_status != 0 AND ss.user_id = cm.user_id and hc.homework_type=4) AS practice_score,
(SELECT IFNULL(sum(eu.score),0) FROM exercise_users AS eu,exercises WHERE eu.exercise_id = exercises.id AND exercises.course_id = #{course.id}
AND eu.commit_status = 1 AND eu.user_id = cm.user_id) AS exercise_score
FROM course_members cm join users on cm.user_id = users.id
WHERE cm.role = 4 and cm.course_id = #{course.id}
}
sql_select += %Q{ and cm.course_group_id in (#{group_ids.join(",")}) } if group_ids.present?
sql_select += %Q{ ) a ORDER BY score #{sort}; }
course_members = CourseMember.find_by_sql(sql_select)
course_members
end
def course_act_scores course, group_ids
sql_select = %Q{ SELECT a.*,
(message_num*2 + message_reply_num + resource_num*5 + homework_journal_num + graduation_num*10 + homework_num*10 + exercise_num*10 + poll_num*7) AS score from
(select cm.*,
(SELECT count(messages.id) FROM messages join boards on messages.board_id = boards.id WHERE boards.course_id = #{course.id}
AND messages.author_id = cm.user_id and messages.parent_id is null) AS message_num,
(SELECT count(messages.id) FROM messages join boards on messages.board_id = boards.id WHERE boards.course_id = #{course.id}
AND messages.author_id = cm.user_id and messages.parent_id is not null) AS message_reply_num,
(SELECT count(attachments.id) FROM attachments WHERE container_id = #{course.id} and container_type = "Course"
AND attachments.author_id = cm.user_id) AS resource_num,
(SELECT count(jfm.id) FROM journals_for_messages AS jfm, homework_commons hs WHERE jfm.jour_id = hs.id AND
jfm.user_id = cm.user_id and jfm.jour_type = "HomeworkCommon" and hs.course_id = #{course.id}) AS homework_journal_num,
(SELECT COUNT(gw.id) FROM graduation_works AS gw, graduation_tasks AS gt WHERE gw.graduation_task_id = gt.id AND
gt.course_id = #{course.id} AND gw.work_status != 0 AND gw.user_id = cm.user_id) AS graduation_num,
(SELECT COUNT(ss.id) FROM student_works AS ss ,homework_commons AS hc WHERE ss.homework_common_id = hc.id AND
hc.course_id = #{course.id} AND ss.work_status != 0 AND ss.user_id = cm.user_id) AS homework_num,
(SELECT COUNT(eu.id) FROM exercise_users AS eu,exercises WHERE eu.exercise_id = exercises.id AND exercises.course_id = #{course.id}
AND eu.commit_status = 1 AND eu.user_id = cm.user_id) AS exercise_num,
(SELECT COUNT(pu.id) FROM poll_users AS pu, polls WHERE pu.poll_id = polls.id AND polls.course_id = #{course.id}
AND pu.commit_status = 1 AND pu.user_id = cm.user_id) AS poll_num
FROM course_members cm join users on cm.user_id = users.id
join user_extensions ue on ue.user_id = users.id
WHERE cm.role = 4 and cm.course_id = #{course.id}
}
sql_select += %Q{ and cm.course_group_id in (#{group_ids.join(",")}) } if group_ids.present?
sql_select += %Q{ ) a ORDER BY score desc; }
course_members = CourseMember.find_by_sql(sql_select)
course_members
end
end

@ -7,22 +7,20 @@ class DiscussesController < ApplicationController
def index
page = params[:page].to_i
offset = page * LIMIT
@manger = @container.has_manager?(current_user) || current_user.is_certification_teacher
# 总数,分页使用
if current_user.admin?
if @manger
@disscuss_count = Discuss.where(:dis_id => @container.id, :dis_type => @container.class.to_s, :root_id => nil).count
disscusses = Discuss.where(:dis_id => @container.id, :dis_type => @container.class.to_s,
:root_id => nil)
@discusses = disscusses.limit(LIMIT).joins("left join games on discusses.challenge_id = games.challenge_id and discusses.user_id = games.user_id")
.select("discusses.*, games.identifier").includes(:user, :praise_treads).offset(offset)
else
disscusses = Discuss.where("dis_id = :dis_id and dis_type = :dis_type and root_id is null and
(discusses.hidden = :hidden or discusses.user_id = :user_id)",
{dis_id: @container.id, dis_type: @container.class.to_s, hidden: false, user_id: current_user.id})
@disscuss_count = disscusses.count("discusses.id")
end
@manger = @container.has_manager?(current_user)
if @manger
@discusses = disscusses.limit(LIMIT).joins("left join games on discusses.challenge_id = games.challenge_id and discusses.user_id = games.user_id")
.select("discusses.*, games.identifier").includes(:user, :praise_treads).offset(offset)
else
@discusses = disscusses.limit(LIMIT).includes(:user, :praise_treads).offset(offset)
end

@ -518,27 +518,27 @@ class ExercisesController < ApplicationController
common_group = exercise_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的
new_group_ids = course_id - common_group #新传入的班级id
if common_group.count > 0 #判断试卷的分班设置是否存在,存在则更新,负责则新建
ex_group_params = {
exercise_group_sets = exercise_groups.find_in_exercise_group("course_group_id",common_group)
exercise_group_sets.each do |the_group_setting|
ex_group_params = {
:publish_time => exercise_publish_time,
:end_time => exercise_end_time
}
exercise_group_sets = exercise_groups.find_in_exercise_group("course_group_id",common_group)
the_group_setting = exercise_group_sets.first
if the_group_setting.present?
}
the_group_setting_status = set_exercise_status(the_group_setting.publish_time,the_group_setting.end_time)
if the_group_setting_status == 2
ex_group_params = {
:publish_time => the_group_setting.publish_time,
:end_time => exercise_end_time
:publish_time => the_group_setting.publish_time,
:end_time => exercise_end_time
}
elsif the_group_setting_status == 3
ex_group_params = {
:publish_time => the_group_setting.publish_time,
:end_time => the_group_setting.end_time
:publish_time => the_group_setting.publish_time,
:end_time => the_group_setting.end_time
}
end
the_group_setting.update_attributes!(ex_group_params)
end
exercise_group_sets.update_all(ex_group_params)
end
if new_group_ids.size > 0
new_group_ids.each do |c|
@ -560,8 +560,9 @@ class ExercisesController < ApplicationController
error_count == 0
normal_status(-1,"已发布/已截止的试卷不允许修改时间")
else
# 未发布的分班设置才能删除
if old_exercise_groups.size > 0
old_all_ex_groups = exercise_groups.find_in_exercise_group("course_group_id",old_exercise_groups)
old_all_ex_groups = exercise_groups.find_in_exercise_group("course_group_id",old_exercise_groups).exercise_group_not_published
old_all_ex_groups.destroy_all
end
#试卷更新为exercise_group_setting的发布时间最小截止时间最大
@ -720,8 +721,8 @@ class ExercisesController < ApplicationController
if exercise.unified_setting
ex_status = exercise.exercise_status #则为试卷的状态
else
ex_status = exercise.exercise_group_settings.find_in_exercise_group("course_group_id",params[:group_ids])
.exercise_group_not_published.present? ? 1 : 0
ex_status = @course.course_groups.where(id: params[:group_ids]).size !=
exercise.exercise_group_settings.where(course_group_id: params[:group_ids]).exercise_group_published.size ? 1 : 0
end
if ex_status == 1 #如果试卷存在已发布的,或者是已截止的,那么则直接跳过
g_course = group_ids #表示是否传入分班参数,如果传入分班的参数,那么试卷的统一设置需修改
@ -739,8 +740,8 @@ class ExercisesController < ApplicationController
g_course.each_with_index do |i, index|
exercise_group_setting = exercise.exercise_group_settings.find_in_exercise_group("course_group_id",i).first #根据课堂分班的id寻找试卷所在的班级
group_end_time = params[:detail] ? group_end_times[index] : ex_end_time
if exercise_group_setting #如果该试卷分组存在,则更新,否则新建
exercise_group_setting.update_attributes(publish_time: Time.now, end_time: group_end_time)
if exercise_group_setting.present? #如果该试卷分组存在,则更新,否则新建
exercise_group_setting.update_attributes!(publish_time: Time.now, end_time: group_end_time)
else
p_course_group = {
:exercise_id => exercise.id,
@ -1128,7 +1129,7 @@ class ExercisesController < ApplicationController
:subjective_score => subjective_score,
:commit_method => @answer_committed_user&.commit_method.to_i > 0 ? @answer_committed_user&.commit_method.to_i : params[:commit_method].to_i
}
@answer_committed_user.update_attributes(commit_option)
@answer_committed_user.update_attributes!(commit_option)
CommitExercsieNotifyJobJob.perform_later(@exercise.id, current_user.id)
normal_status(0,"试卷提交成功!")
else
@ -1240,6 +1241,8 @@ class ExercisesController < ApplicationController
#筛选/分类,排序
order = params[:order]
order_type = params[:order_type] || "desc"
if @exercise_users_list.present? && @exercise_users_list.size > 0
@exercise_users_count = @exercise_users_list.size #当前显示的全部成员数量
teacher_reviews = @exercise_users_list.exercise_review
@ -1279,11 +1282,11 @@ class ExercisesController < ApplicationController
exercise_user_joins = @exercise_users_list.joins(user: :user_extension)
if order == "student_id"
@exercise_users_list = exercise_user_joins.order("user_extensions.student_id DESC")
@exercise_users_list = exercise_user_joins.order("user_extensions.student_id #{order_type}")
elsif order == "score"
@exercise_users_list = exercise_user_joins.order("#{order} DESC")
@exercise_users_list = exercise_user_joins.order("#{order} #{order_type}")
else
@exercise_users_list = exercise_user_joins.order("end_at DESC, start_at DESC")
@exercise_users_list = exercise_user_joins.order("end_at #{order_type}, start_at #{order_type}")
end
@export_ex_users = @exercise_users_list
@ -1406,11 +1409,16 @@ class ExercisesController < ApplicationController
min_score = exercise_scores.min.present? ? exercise_scores.min : 0.0
max_score = exercise_scores.max.present? ? exercise_scores.max : 0.0
total_score = exercise_scores.sum.present? ? exercise_scores.sum : 0.0
average_score = @exercise_commit_user_counts > 0 ? (total_score / @exercise_commit_user_counts).round(1) : 0.0
fail_counts = exercise_scores.count{|a| a < 60.0}
pass_counts = exercise_scores.count{|a| a < 70.0 && a >= 60.0}
good_counts = exercise_scores.count{|a| a < 90.0 && a >= 70.0}
best_counts = exercise_scores.count{|a| a >= 90.0 && a <= 100.0}
average_score = @exercise_commit_user_counts > 0 ? (total_score.round(1) / @exercise_commit_user_counts).round(1) : 0.0
question_scores = @exercise.question_scores
fail_score = question_scores * 0.6.round(2)
pass_score = question_scores * 0.7.round(2)
good_score = question_scores * 0.9.round(2)
fail_counts = exercise_scores.count{|a| a < fail_score}
pass_counts = exercise_scores.count{|a| a < pass_score && a >= fail_score}
good_counts = exercise_scores.count{|a| a < good_score && a >= pass_score}
best_counts = exercise_scores.count{|a| a >= good_score && a <= question_scores}
end
@counts_array = {
:commit_percent => commit_percent,
@ -1425,18 +1433,24 @@ class ExercisesController < ApplicationController
@exercise_questions = @exercise.exercise_questions&.includes(:exercise_choices,:exercise_answers,:exercise_standard_answers,:exercise_shixun_challenges,:exercise_shixun_answers)
@paging_type = "percent"
# 按题型排序
percent_sort = "desc"
if params[:sort].present?
@paging_type = params[:sort].to_s
percent_sort = params[:sort]
end
# @paging_type = "percent"
# # 按题型排序
# if params[:sort].present?
# @paging_type = params[:sort].to_s
# end
ques_result_all = exercise_commit_result(@exercise_questions,@exercise_commit_user_ids)
if @paging_type == "percent"
@question_result_hash = ques_result_all.sort_by{|s| s[:percent]}
#默认降序排列
if percent_sort == "desc"
@question_result_hash = ques_result_all.sort_by{|s| s[:percent]}.reverse
else
@question_result_hash = ques_result_all.sort_by{|s| s[:"#{@paging_type}"]}
@question_result_hash = ques_result_all.sort_by{|s| s[:percent]}
end
@exercise_questions_count = @exercise_questions.size

@ -55,7 +55,7 @@ class GamesController < ApplicationController
# 选择题和编程题公共部分
@base_date = {st: @st, discusses_count: discusses_count, game_count: game_count, myshixun: @myshixun,
challenge: game_challenge.attributes.except("answer"), game: @game.try(:attributes), shixun: @shixun.try(:attributes),
challenge: game_challenge.attributes.except("answer"), game: @game.try(:attributes), shixun: @shixun.attributes.except("vnc", "vnc_evaluate"),
record_onsume_time: record_onsume_time, prev_game: prev_game, next_game: next_game,
praise_count: praise_count, user_praise: user_praise, time_limit: time_limit,
tomcat_url: edu_setting('cloud_tomcat_php'), is_teacher: is_teacher,

@ -2,10 +2,12 @@ class HomeController < ApplicationController
def index
# banner图
images = PortalImage.where(status: true).order("position asc")
images = current_laboratory.portal_images.only_online.order(position: :asc)
images = default_laboratory.portal_images.only_online.order(position: :asc) if images.blank? # 未设置时使用EduCoder的轮播图
@images_url = []
images.each do |image|
@images_url << {path: image.link, image_url: Util::FileManage.disk_file_url('PortalImage', image.id)}
@images_url << {path: image.link, image_url: Util::FileManage.source_disk_file_url(image)}
end
# 目录分级

@ -439,6 +439,8 @@ class HomeworkCommonsController < ApplicationController
def settings
@user = current_user
@work = @homework.user_work(current_user.id) if @user_course_identity == Course::STUDENT
@course_groups = @course.course_groups.where(id: @course.charge_group_ids(@user))
@shixun = @homework.shixuns.take if @homework.homework_type == "practice"
end
def update_settings
@ -1058,8 +1060,8 @@ class HomeworkCommonsController < ApplicationController
tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")}") if
@course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day)
else
tip_exception("缺少分班截止时间参数") if params[:group_end_times].blank?
group_end_times = params[:group_end_times].reject(&:blank?).map{|time| time.to_time}
tip_exception("缺少截止时间参数") if group_end_times.blank?
tip_exception("截止时间和分班参数的个数不一致") if group_end_times.length != group_ids.length
group_end_times.each do |time|
tip_exception("分班截止时间不能早于当前时间") if time <= Time.now

@ -0,0 +1,7 @@
class HotKeywordsController < ApplicationController
def index
keywords = []
keywords = HotSearchKeyword.hot(8) if HotSearchKeyword.available?
render_ok(keywords: keywords)
end
end

@ -90,7 +90,7 @@ class MyshixunsController < ApplicationController
ActiveRecord::Base.transaction do
begin
t1 = Time.now
Rails.logger.info("@@@222222#{params[:jsonTestDetails]}")
uid_logger_dubug("@@@222222#{params[:jsonTestDetails]}")
jsonTestDetails = JSON.parse(params[:jsonTestDetails])
timeCost = JSON.parse(params[:timeCost])
brige_end_time = Time.parse(timeCost['evaluateEnd']) if timeCost['evaluateEnd'].present?
@ -99,7 +99,7 @@ class MyshixunsController < ApplicationController
game_id = jsonTestDetails['buildID']
sec_key = jsonTestDetails['sec_key']
logger.info("training_task_status start#1**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
uid_logger_dubug("training_task_status start-#{game_id}-1#{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
resubmit = jsonTestDetails['resubmit']
outPut = tran_base64_decode64(jsonTestDetails['outPut'])
@ -116,17 +116,14 @@ class MyshixunsController < ApplicationController
pics = params[:tpiRepoPath]
game.update_column(:picture_path, pics)
end
logger.info("training_task_status start#2**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
max_query_index = game.outputs ? (game.outputs.first.try(:query_index).to_i + 1) : 1
test_set_score = 0
unless jenkins_testsets.blank?
jenkins_testsets.each_with_index do |j_test_set, i|
logger.info("j_test_set: ############## #{j_test_set}")
actual_output = tran_base64_decode64(j_test_set['output'])
#ts_time += j_test_set['testSetTime'].to_i
# is_public = test_sets.where(:position => j_test_set['caseId']).first.try(:is_public)
logger.info "actual_output:################################################# #{actual_output}"
ts_time = format("%.2f", j_test_set['testSetTime'].to_f/1000000000).to_f if j_test_set['testSetTime']
ts_mem = format("%.2f", j_test_set['testSetMem'].to_f/1024/1024).to_f if j_test_set['testSetMem']
@ -139,15 +136,12 @@ class MyshixunsController < ApplicationController
end
end
end
uid_logger("#############status: #{status}")
record = EvaluateRecord.where(:identifier => sec_key).first
logger.info("training_task_status start#3**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
answer_deduction_percentage = (100 - game.answer_deduction) / 100.to_f # 查看答案后剩余分数的百分比.
# answer_deduction是查看答案的扣分比例
# status0表示评测成功
if status == "0"
if resubmit.present?
uid_logger("#############resubmitdaiao: #{resubmit}")
game.update_attributes!(:retry_status => 2, :resubmit_identifier => resubmit)
challenge.path.split("").each do |path|
game_passed_code(path.try(:strip), myshixun, game_id)
@ -205,7 +199,6 @@ class MyshixunsController < ApplicationController
:pod_execute => timeCost['execute'], :test_cases => test_cases_time,
:brige => timeCost['evaluateAllTime'], :return_back => return_back_time)
end
uid_logger("training_task_status start#4**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
sucess_status
# rescue Exception => e
# tip_exception(e.message)
@ -265,19 +258,21 @@ class MyshixunsController < ApplicationController
@sec_key = generate_identifier(EvaluateRecord, 12)
record = EvaluateRecord.create!(:user_id => current_user.id, :shixun_id => @myshixun.shixun_id, :game_id => game_id,
:identifier => @sec_key, :exec_time => exec_time)
uid_logger("-- game build: file update #{@sec_key}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
uid_logger_dubug("-- game build: file update #{@sec_key}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
end
# 隐藏代码文件 和 VNC的都不需要走版本库
unless @hide_code || @myshixun.shixun&.vnc_evaluate
unless @hide_code || (@myshixun.shixun&.vnc_evaluate && params[:evaluate].present?)
# 远程版本库文件内容
last_content = GitService.file_content(repo_path: @repo_path, path: path)["content"]
content = if @myshixun.mirror_name.select {|a| a.include?("MachineLearning") || a.include?("Python")}.present? && params[:content].present?
params[:content].gsub(/\t/, ' ').gsub(/ /, ' ') # 这个不是空格在windows机器上带来的问题
else
params[:content]
end
Rails.logger.info("###11222333####{content}")
Rails.logger.info("###222333####{last_content}")
content =
if python_file?(path)
params[:content].gsub(/\t/, ' ').gsub(/ /, ' ')
else
params[:content]
end
uid_logger_dubug("###11222333####{content}")
uid_logger_dubug("###222333####{last_content}")
if content != last_content
@content_modified = 1
@ -285,8 +280,8 @@ class MyshixunsController < ApplicationController
author_name = current_user.real_name
author_email = current_user.git_mail
message = params[:evaluate] == 0 ? "System automatically submitted" : "User submitted"
uid_logger("112233#{author_name}")
uid_logger("112233#{author_email}")
uid_logger_dubug("112233#{author_name}")
uid_logger_dubug("112233#{author_email}")
@content = GitService.update_file(repo_path: @repo_path,
file_path: path,
message: message,
@ -381,4 +376,9 @@ class MyshixunsController < ApplicationController
@repo_path = @myshixun.try(:repo_path)
@path = params[:path]
end
def python_file?(path)
false if path.blank?
path.to_s.split(".").last.downcase == "py"
end
end

@ -16,9 +16,9 @@ class PollVotesController < ApplicationController
# 当前用户的当前答案,如果已存在,当再次点击的时候,取消答案,即删除该答案
current_vote_text = nil
if user_votes.find_vote_text.present?
current_vote_text = user_votes.find_vote_text.first
end
# if user_votes.find_vote_text.present?
# current_vote_text = user_votes.find_vote_text.first
# end
vote_answer_params = {
:user_id => current_user.id,
@ -36,7 +36,6 @@ class PollVotesController < ApplicationController
else
if question_answer_text.present?
current_user_answer.update_attribute("vote_text", question_answer_text)
end
end
@ -48,17 +47,23 @@ class PollVotesController < ApplicationController
if question_answer_ids.present?
if question_answer_text.present? #有文字输入,但是不存在其他选项的
ques_vote_id = question_answer_ids.map(&:to_i).max
if current_vote_text.present? #已有其他输入文字的选项
if user_votes.find_vote_text.present?
current_vote_text = user_votes.find_vote_text.first
current_vote_text.update_attribute("vote_text", question_answer_text)
else
answer_option = {
:user_id => current_user.id,
:poll_question_id => @poll_question.id,
:poll_answer_id => ques_vote_id,
:vote_text => question_answer_text
:user_id => current_user.id,
:poll_question_id => @poll_question.id,
:poll_answer_id => ques_vote_id,
:vote_text => question_answer_text
}
PollVote.create(answer_option)
end
# if current_vote_text.present? #已有其他输入文字的选项
# current_vote_text.update_attribute("vote_text", question_answer_text)
# else
#
# end
end
ea_ids = user_votes.pluck(:poll_answer_id)
@ -85,12 +90,14 @@ class PollVotesController < ApplicationController
user_votes.destroy_all
end
else #主观题的输入
if current_vote_text.present?
if question_answer_text.present?
user_votes.first.update_attribute("vote_text", question_answer_text)
else
user_votes.destroy_all
end
# current_vote_text = user_votes.find_vote_text
if user_votes.present?
user_votes.first.update_attribute("vote_text", question_answer_text)
# if question_answer_text.present?
# user_votes.first.update_attribute("vote_text", question_answer_text)
# else
# user_votes.destroy_all
# end
else
PollVote.create(vote_answer_params)
end

@ -279,7 +279,8 @@ class PollsController < ApplicationController
if poll.unified_setting
pl_status = poll.polls_status #则为试卷的状态
else
pl_status = poll.poll_group_settings.find_in_poll_group("course_group_id",params[:group_ids]).poll_group_not_published.present? ? 1 : 0 #立即发布针对分组设置的全部未发布的班级才生效
pl_status = @course.course_groups.where(id: group_ids).size !=
poll.poll_group_settings.where(course_group_id: group_ids).poll_group_published.size ? 1 : 0 #立即发布针对分组设置的全部未发布的班级才生效
end
if pl_status == 1 #如果问卷存在已发布的,或者是已截止的,那么则直接跳过
g_course = group_ids #表示是否传入分班参数,如果传入分班的参数那么poll的统一设置需修改
@ -295,8 +296,8 @@ class PollsController < ApplicationController
g_course.each_with_index do |i, index|
poll_group_setting = poll.poll_group_settings.find_in_poll_group("course_group_id",i).first #根据课堂分班的id寻找问卷所在的班级
group_end_time = params[:detail] ? group_end_times[index] : ex_end_time
if poll_group_setting #如果该问卷分组存在,则更新,否则新建
poll_group_setting.update_attributes(publish_time: Time.now, end_time: group_end_time)
if poll_group_setting.present? #如果该问卷分组存在,则更新,否则新建
poll_group_setting.update_attributes!(publish_time: Time.now, end_time: group_end_time)
else
p_course_group = {
:poll_id => poll.id,
@ -306,7 +307,7 @@ class PollsController < ApplicationController
:end_time => group_end_time,
}
new_poll_group = poll.poll_group_settings.new p_course_group
new_poll_group.save
new_poll_group.save!
end
end
e_time = poll.poll_group_settings.end_time_present.map(&:end_time).max
@ -782,27 +783,27 @@ class PollsController < ApplicationController
common_group = poll_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的
new_group_ids = course_id - common_group #新传入的班级id
if common_group.size > 0 #传入的参数存在已发布的分班
poll_group_params = {
poll_group = poll_groups.find_in_poll_group("course_group_id",common_group)
poll_group.each do |the_group_setting|
poll_group_params = {
:publish_time => poll_publish_time,
:end_time => poll_end_time
}
poll_group = poll_groups.find_in_poll_group("course_group_id",common_group)
the_group_setting = poll_group.first
if the_group_setting.present?
}
the_group_setting_status = set_poll_status(the_group_setting.publish_time,the_group_setting.end_time)
if the_group_setting_status == 2
poll_group_params = {
:publish_time => the_group_setting.publish_time,
:end_time => poll_end_time
:publish_time => the_group_setting.publish_time,
:end_time => poll_end_time
}
elsif the_group_setting_status == 3
poll_group_params = {
:publish_time => the_group_setting.publish_time,
:end_time => the_group_setting.end_time
:publish_time => the_group_setting.publish_time,
:end_time => the_group_setting.end_time
}
end
the_group_setting.update_attributes(poll_group_params)
end
poll_group.update_all(poll_group_params)
end
if new_group_ids.size > 0
new_group_ids.each do |c|
@ -824,8 +825,9 @@ class PollsController < ApplicationController
error_count == 0
normal_status(-1,"已发布/已截止的问卷不允许修改时间")
else
# 未发布的分班设置才能删除
if old_poll_groups.size > 0
old_all_poll_groups = poll_groups.find_in_poll_group("course_group_id",old_poll_groups)
old_all_poll_groups = poll_groups.find_in_poll_group("course_group_id",old_poll_groups).poll_group_not_published
old_all_poll_groups.destroy_all
end
#问卷更新为poll_group_setting的发布时间最小截止时间最大
@ -898,12 +900,30 @@ class PollsController < ApplicationController
# 判断是否已经回答还是新建的回答
@poll_questions.each do |q|
ques_vote = q.poll_votes.find_current_vote("user_id",@poll_current_user_id)
if ques_vote.present?
ques_status = 1
question_answered += 1
ques_type = q.question_type
if ques_type != 3 #非简答题时
if ques_vote.exists?
ques_status = 1
question_answered += 1
else
ques_status = 0
end
else
ques_status = 0
if ques_vote.find_vote_text.first.present?
ques_status = 1
question_answered += 1
else
ques_status = 0
end
end
# if ques_vote.present?
# ques_status = 1
# question_answered += 1
# else
# ques_status = 0
# end
answer_status = {
:ques_id => q.id,
:ques_number => q.question_number,
@ -1048,7 +1068,7 @@ class PollsController < ApplicationController
#筛选/分类,排序
order = params[:order]
# b_sort = params[:sort] || "desc"
order_type = params[:order_type] || "desc"
choose_type = params[:commit_status]
group_id = params[:poll_group_id]
search_content = params[:search]
@ -1075,9 +1095,9 @@ class PollsController < ApplicationController
poll_users_joins = @poll_users_list.joins(user: :user_extension)
if order == "student_id"
@poll_users_list = poll_users_joins.order("user_extensions.student_id DESC")
@poll_users_list = poll_users_joins.order("user_extensions.student_id #{order_type}")
else
@poll_users_list = poll_users_joins.order("end_at DESC")
@poll_users_list = poll_users_joins.order("end_at #{order_type}")
end
@poll_users_size = @poll_users_list.count
@ -1343,7 +1363,7 @@ class PollsController < ApplicationController
poll_ques_titles = poll_questions.pluck(:question_title).map {|k| ActionController::Base.helpers.strip_tags(k) if k.present?}
poll_un_anony = poll.un_anonymous
if poll_un_anony #是否匿名默认为false
user_info = %w(登陆名 真实姓名 邮箱 学号 学员单位)
user_info = %w(登陆名 真实姓名 分班 邮箱 学号 学员单位)
else
user_info = []
end
@ -1416,9 +1436,10 @@ class PollsController < ApplicationController
if poll_un_anony
user_login = u_user.login
user_name = u_user.real_name.present? ? u_user.real_name : "--"
user_class = poll.course.user_group_name(u_user.id)
user_student_id = u_user.student_id.present? ? u_user.student_id : "--"
user_school_name = u_user.school_name.present? ? u_user.school_name : "--"
user_cell += [user_login,user_name, u_user.mail, user_student_id, user_school_name]
user_cell += [user_login,user_name, user_class, u_user.mail, user_student_id, user_school_name]
end
all_user_cell = user_cell + user_answer_array
user_commit.push(all_user_cell)

@ -11,6 +11,10 @@ class SchoolsController < ApplicationController
end
def for_option
render_ok(schools: School.select(:id, :name).as_json)
schools = School.all
keyword = params[:keyword].to_s.strip
schools = schools.where('name LIKE ?', "%#{keyword}%") if keyword
render_ok(schools: schools.select(:id, :name).as_json)
end
end

@ -1,9 +1,12 @@
class SearchsController < ApplicationController
after_action :record_search_keyword, only: [:index]
def index
@results = SearchService.call(search_params)
end
private
def search_params
params.permit(:keyword, :type, :page, :per_page)
end

@ -14,7 +14,7 @@ class ShixunsController < ApplicationController
before_action :shixun_access_allowed, except: [:index, :new, :create, :menus, :get_recommend_shixuns,
:propaedeutics, :departments, :apply_shixun_mirror,
:get_mirror_script, :download_file, :shixun_list]
:get_mirror_script, :download_file, :shixun_list, :review_shixuns]
before_action :find_repo_name, only: [:repository, :commits, :file_content, :update_file, :shixun_exec, :copy, :add_file]
before_action :allowed, only: [:update, :close, :update_propaedeutics, :settings, :publish,
@ -232,10 +232,12 @@ class ShixunsController < ApplicationController
# 同步私密版本库
if @shixun.shixun_secret_repository
repo_name = "#{current_user.login}/secret_#{@shixun.identifier}"
# 源仓库的的私密版本库地址
repo_name = @shixun.shixun_secret_repository.repo_name
# 新生成的地址
fork_repository_name = "#{current_user.login}/secret_#{@new_shixun.identifier}"
ShixunSecretRepository.create!(shixun_id: @new_shixun.id,
repo_name: "#{repo_name}",
repo_name: "#{fork_repository_name}",
secret_dir_path: @shixun.shixun_secret_repository.secret_dir_path)
GitService.fork_repository(repo_path: "#{repo_name}.git", fork_repository_path: (fork_repository_name + ".git"))
end
@ -262,6 +264,11 @@ class ShixunsController < ApplicationController
:mirror_repository_id => config.mirror_repository_id)
end
# 同步高校限制
@shixun.shixun_schools.each do |school|
ShixunSchool.create!(shixun_id: @new_shixun.id, school_id: school.school_id)
end
# fork版本库
logger.info("###########fork_repo_path: ######{@repo_path}")
project_fork(@new_shixun, @repo_path, current_user.login)
@ -330,7 +337,10 @@ class ShixunsController < ApplicationController
end
rescue Exception => e
uid_logger_error("copy shixun failed ##{e.message}")
g.delete_project(gshixungshixun.id) if gshixun.try(:id).present? # 异常后,如果已经创建了版本库需要删除该版本库
# 删除版本库
# 删除私密版本库
GitService.delete_repository(repo_path: "#{fork_repository_name}.git") if @new_shixun.shixun_secret_repository&.repo_name
GitService.delete_repository(repo_path: @new_shixun.repo_path) if @new_shixun.repo_path
tip_exception("实训Fork失败")
raise ActiveRecord::Rollback
end
@ -986,6 +996,21 @@ class ShixunsController < ApplicationController
@shixun.update_column(:status, 0)
end
# 创建实训审核
def review_shixun
validate_review_shixun_params
# 没有记录就创建记录, 如果有记录就
@shixun.shixun_reviews.create!(user_id: current_user.id, status: params[:status],
review_type: params[:review_type], evaluate_content: params[:evaluate_content])
normal_status("审核完成")
end
# 实训审核最新记录
def review_newest_record
@content_record = @shixun.shixun_reviews.where(review_type: "Content").first
@perfer_record = @shixun.shixun_reviews.where(review_type: "Performance").first
end
private
def shixun_params
raise("实训名称不能为空") if params[:shixun][:name].blank?
@ -994,6 +1019,11 @@ private
:hide_code, :forbid_copy, :vnc_evaluate, :code_edit_permission)
end
def validate_review_shixun_params
tip_exception("只有平台管理员或运营人员才能审核") if !admin_or_business?
tip_exception("审核类型参数不对") unless ["Content", "Performance"].include?(params[:review_type])
end
def shixun_info_params
raise("实训描述不能为空") if params[:shixun_info][:description].blank?
raise("评测脚本不能为空") if params[:shixun_info][:evaluate_script].blank?

@ -11,6 +11,6 @@ class Users::AuthenticationAppliesController < Users::BaseAccountController
private
def create_params
params.permit(:name, :id_number, :upload_image)
params.permit(:name, :gender, :id_number, :upload_image)
end
end

@ -0,0 +1,13 @@
class Users::OpenUsersController < Users::BaseAccountController
def destroy
current_open_users.destroy!
render_ok
end
private
def current_open_users
@_current_third_party ||= observed_user.open_users.find(params[:id])
end
end

@ -0,0 +1,37 @@
class Weapps::BaseController < ApplicationController
private
def require_wechat_login!
Rails.logger.info("[Weapp] unionid: #{session_unionid}, openid: #{session_openid}")
return if session_unionid.present?
render_error('请先进行微信授权')
end
def weapp_session_key
Wechat::Weapp.session_key(session_openid)
end
def set_weapp_session_key(session_key)
Wechat::Weapp.write_session_key(session_openid, session_key)
end
def session_openid
session[:openid]
end
def set_session_openid(openid)
Rails.logger.info("[Weapp] set session openid: #{openid}")
session[:openid] = openid
end
def session_unionid
session[:unionid]
end
def set_session_unionid(unionid)
Rails.logger.info("[Weapp] set session unionid: #{unionid}")
session[:unionid] = unionid
end
end

@ -0,0 +1,38 @@
class Weapps::CheckAccountsController < Weapps::BaseController
def create
params[:type] == 'register' ? check_can_register : check_can_bind
end
private
def check_can_bind
if params[:login] =~ /^[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/
user = User.find_by(mail: params[:login])
return render_error('该邮箱尚未注册') if user.blank?
elsif params[:login] =~ /^1\d{10}$/
user = User.find_by(phone: params[:login])
return render_error('该手机号尚未注册') if user.blank?
else
user = User.find_by(login: params[:login])
return render_error('该账号尚未注册') if user.blank?
end
return render_error('该账号已经绑定') if user.wechat_open_user.present?
render_ok
end
def check_can_register
if params[:login] =~ /^[a-zA-Z0-9]+([._\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/
user = User.find_by(mail: params[:login])
return render_error('该邮箱已注册') if user.present?
elsif params[:login] =~ /^1\d{10}$/
user = User.find_by(phone: params[:login])
return render_error('该手机号已注册') if user.present?
else
return render_error('请输入正确的邮箱或手机号')
end
render_ok
end
end

@ -0,0 +1,39 @@
class Weapps::CodeSessionsController < Weapps::BaseController
def create
return render_error('code不能为空') if params[:code].blank?
reset_session
logged = false
result = Wechat::Weapp.jscode2session(params[:code])
# 能根据 code 拿到 unionid
open_user = OpenUsers::Wechat.find_by(uid: result['unionid'])
if open_user.present? && open_user.user
successful_authentication(open_user.user)
set_session_unionid(result['unionid'])
logged = true
else
# 根据 code没拿到 unionid
user_info = Wechat::Weapp.decrypt(result['session_key'], params[:encrypted_data], params[:iv])
# 老用户,已绑定
open_user = OpenUsers::Wechat.find_by(uid: user_info['unionId'])
if open_user.present? && open_user.user
successful_authentication(open_user.user)
logged = true
end
set_session_unionid(user_info['unionId'])
user_info['nickname'] = user_info['nickName']
session[:wechat_user_extra] = user_info
end
set_session_openid(result['openid'])
set_weapp_session_key(result['session_key']) # weapp session_key写入缓存 后续解密需要
render_ok(openid: result['openid'], logged: logged) unless logged
rescue Wechat::Error => ex
render_error(ex.message)
end
end

@ -0,0 +1,15 @@
class Weapps::HomesController < Weapps::BaseController
def show
# banner
@carousels = WeappSettings::Carousel.only_online
# 广告
@advert = WeappSettings::Advert.only_online.first
# 热门实训
@shixuns = Shixun.where(homepage_show: true).includes(:tag_repertoires, :challenges).limit(4)
# 热门实践课程
@subjects = Subject.where(homepage_show: true).includes(:shixuns, :repertoire).limit(4)
end
end

@ -0,0 +1,63 @@
class Weapps::RegistersController < Weapps::BaseController
before_action :require_wechat_login!
def create
# 查询验证码是否正确;type只可能是1或者8
type = phone_mail_type(params[:login].strip)
code = params[:code].strip
if type == 1
uid_logger("start register by phone: type is #{type}")
pre = 'p'
email = nil
phone = params[:login]
verifi_code = VerificationCode.where(phone: phone, code: code, code_type: 1).last
else
uid_logger("start register by email: type is #{type}")
pre = 'm'
email = params[:login]
phone = nil
verifi_code = VerificationCode.where(email: email, code: code, code_type: 8).last
end
uid_logger("start register: verifi_code is #{verifi_code}, code is #{code}, time is #{Time.now.to_i - verifi_code.try(:created_at).to_i}")
# check_code = (verifi_code.try(:code) == code.strip && (Time.now.to_i - verifi_code.created_at.to_i) <= 10*60)
# todo 上线前请删除万能验证码"513231"
unless code == "513231" && request.subdomain == "test-newweb"
return render_error('验证码不正确') if verifi_code.try(:code) != code.strip
return render_error('验证码已失效') if !verifi_code&.effective?
end
login = User.generate_login(pre)
@user = User.new(admin: false, login: login, mail: email, phone: phone, type: 'User')
@user.password = params[:password]
# 现在因为是验证码,所以在注册的时候就可以激活
@user.activate
# 必须要用save操作密码的保存是在users中
ActiveRecord::Base.transaction do
@user.save!
UserExtension.create!(user_id: @user.id)
# 绑定微信号
OpenUsers::Wechat.create!(user: @user, uid: session_unionid)
# 注册完成手机号或邮箱想可以奖励500金币
RewardGradeService.call(
@user,
container_id: @user.id,
container_type: pre == 'p' ? 'Phone' : 'Mail',
score: 500
)
end
successful_authentication(@user)
session[:user_id] = @user.id
render_ok
end
private
# 1 手机类型0 邮箱类型
# 注意新版的login是自动名生成的
def phone_mail_type value
value =~ /^1\d{10}$/ ? 1 : 0
end
end

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save