本地版调整

dev_local_2
杨树林 5 years ago
commit 93648dffeb

@ -98,3 +98,7 @@ gem 'aasm'
gem 'enumerize' gem 'enumerize'
gem 'diffy' gem 'diffy'
# oauth2
gem 'omniauth', '~> 1.9.0'
gem 'omniauth-oauth2', '~> 1.6.0'

@ -121,7 +121,7 @@ GEM
grape-entity (0.7.1) grape-entity (0.7.1)
activesupport (>= 4.0) activesupport (>= 4.0)
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
hashie (3.6.0) hashie (3.5.7)
htmlentities (4.3.4) htmlentities (4.3.4)
httparty (0.16.2) httparty (0.16.2)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
@ -179,6 +179,12 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
rack (>= 1.6.2, < 3)
omniauth-oauth2 (1.6.0)
oauth2 (~> 1.1)
omniauth (~> 1.9)
pdfkit (0.8.4.1) pdfkit (0.8.4.1)
popper_js (1.14.5) popper_js (1.14.5)
public_suffix (4.0.1) public_suffix (4.0.1)
@ -381,6 +387,8 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)
mysql2 (>= 0.4.4, < 0.6.0) mysql2 (>= 0.4.4, < 0.6.0)
oauth2 oauth2
omniauth (~> 1.9.0)
omniauth-oauth2 (~> 1.6.0)
pdfkit pdfkit
puma (~> 3.11) puma (~> 3.11)
rack-cors rack-cors
@ -415,4 +423,4 @@ RUBY VERSION
ruby 2.3.7p456 ruby 2.3.7p456
BUNDLED WITH BUNDLED WITH
1.17.3 2.0.2

@ -43,7 +43,7 @@
*(前端)1、educoder/public/images/educoder/headNavLogo.png *(前端)1、educoder/public/images/educoder/headNavLogo.png
三 替换后端管理页面图标 三 替换后端管理页面图标
*(前端)1、educoder/app/assets/images/logo.png *(前端)1、educoder/app/assets/images/logo.png
改动文件后还要执行rake assets:precompile 改动文件后还要执行 rake assets:precompile
15、标签企业名称修改(****需要重新build) 15、标签企业名称修改(****需要重新build)
一、 全局搜索<title>EduCoder</title> 替换掉 <title>替换的公司名称</title> 一、 全局搜索<title>EduCoder</title> 替换掉 <title>替换的公司名称</title>
改动文件后还要执行rake assets:precompile 改动文件后还要执行rake assets:precompile

@ -1,5 +1,6 @@
# README # README
https://www.trustie.net/issues/24719
[云上实验室] Logo、导航、底部备案信息定制化
This README would normally document whatever steps are necessary to get the This README would normally document whatever steps are necessary to get the
application up and running. application up and running.

@ -1,5 +1,7 @@
$(document).on('turbolinks:load', function() { $(document).on('turbolinks:load', function() {
if ($('body.admins-carousels-index-page').length > 0) { if ($('body.admins-carousels-index-page').length > 0) {
var laboratoryId = $('#carousels-container').data('laboratoryId');
// ------------ 保存链接 ----------- // ------------ 保存链接 -----------
$('.carousels-card').on('click', '.save-data-btn', function(){ $('.carousels-card').on('click', '.save-data-btn', function(){
var $link = $(this); var $link = $(this);
@ -13,7 +15,7 @@ $(document).on('turbolinks:load', function() {
$link.attr('disabled', true); $link.attr('disabled', true);
$.ajax({ $.ajax({
url: '/admins/carousels/' + id, url: '/admins/laboratories/' + laboratoryId + '/carousels/' + id,
method: 'PATCH', method: 'PATCH',
dataType: 'json', dataType: 'json',
data: { link: link, name: name }, data: { link: link, name: name },
@ -34,7 +36,7 @@ $(document).on('turbolinks:load', function() {
$checkbox.attr('disabled', true); $checkbox.attr('disabled', true);
$.ajax({ $.ajax({
url: '/admins/carousels/' + id, url: '/admins/laboratories/' + laboratoryId + '/carousels/' + id,
method: 'PATCH', method: 'PATCH',
dataType: 'json', dataType: 'json',
data: { status: checked }, data: { status: checked },
@ -60,7 +62,7 @@ $(document).on('turbolinks:load', function() {
var insertId = $(sibling).data('id') || ''; var insertId = $(sibling).data('id') || '';
$.ajax({ $.ajax({
url: '/admins/carousels/drag', url: '/admins/laboratories/' + laboratoryId + '/carousels/drag',
method: 'POST', method: 'POST',
dataType: 'json', dataType: 'json',
data: { move_id: moveId, after_id: insertId }, data: { move_id: moveId, after_id: insertId },

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

@ -0,0 +1,91 @@
//= 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.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,125 @@
$(document).on('turbolinks:load', function() {
if ($('body.cooperative-carousels-index-page').length > 0) {
// ------------ 保存链接 -----------
$('.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){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
},
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 item.real_name + "--" + item.identity;
},
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');
}
});

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

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

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

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

@ -183,20 +183,6 @@ class AccountsController < ApplicationController
end end
private private
def autologin_cookie_name
edu_setting('autologin_cookie_name') || 'autologin'
end
def logout_user
if User.current.logged?
if autologin = cookies.delete(autologin_cookie_name)
User.current.delete_autologin_token(autologin)
end
User.current.delete_session_token(session[:tk])
self.logged_user = nil
end
session[:user_id] = nil
end
# type 事件类型 1用户注册 2忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验证手机号是否有效 # 如果有新的继续后面加 # type 事件类型 1用户注册 2忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验证手机号是否有效 # 如果有新的继续后面加
# login_type 1手机类型 2邮箱类型 # login_type 1手机类型 2邮箱类型

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

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

@ -0,0 +1,20 @@
class Admins::CompetitionSettingsController < Admins::BaseController
def show
@competition = current_competition
end
def update
Admins::SaveLaboratorySettingService.call(current_competition, form_params)
render_ok
end
private
def current_competition
@_current_competition ||= Competition.find(params[:competition_id])
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,46 @@
class Admins::CompetitionsController < Admins::BaseController
include CustomSortable
before_action :find_competition, except: [:index]
def index
params[:sort_by] = params[:sort_by].presence || 'created_on'
params[:sort_direction] = params[:sort_direction].presence || 'desc'
@competitions = custom_sort Competition.all, params[:sort_by], params[:sort_direction]
@params_page = params[:page] || 1
@competitions = paginate @competitions
ids = @competitions.map(&:id)
@member_count_map = TeamMember.where(competition_id: ids).group(:competition_id).count
respond_to do |format|
format.js
format.html
end
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
def enroll_list
end
private
def find_competition
@competition = Competition.find_by(id: params[:id])
end
end

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

@ -8,6 +8,7 @@ class ApplicationController < ActionController::Base
include GitHelper include GitHelper
include LoggerHelper include LoggerHelper
include LaboratoryHelper include LaboratoryHelper
include LoginHelper
protect_from_forgery prepend: true, unless: -> { request.format.json? } protect_from_forgery prepend: true, unless: -> { request.format.json? }
@ -235,12 +236,6 @@ class ApplicationController < ActionController::Base
# end # end
end end
def start_user_session(user)
session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i
end
def user_setup def user_setup
# reacct静态资源加载不需要走这一步 # reacct静态资源加载不需要走这一步
return if params[:controller] == "main" return if params[:controller] == "main"
@ -281,17 +276,6 @@ class ApplicationController < ActionController::Base
# User.current = User.find 81403 # User.current = User.find 81403
end end
# Sets the logged in user
def logged_user=(user)
reset_session
if user && user.is_a?(User)
User.current = user
start_user_session(user)
else
User.current = User.anonymous
end
end
# Returns the current user or nil if no user is logged in # Returns the current user or nil if no user is logged in
# and starts a session if needed # and starts a session if needed
def find_current_user def find_current_user
@ -307,10 +291,6 @@ class ApplicationController < ActionController::Base
end end
end end
def autologin_cookie_name
edu_setting('autologin_cookie_name').presence || 'autologin'
end
def try_to_autologin def try_to_autologin
if cookies[autologin_cookie_name] if cookies[autologin_cookie_name]
# auto-login feature starts a new session # auto-login feature starts a new session
@ -358,9 +338,9 @@ class ApplicationController < ActionController::Base
# 如果代码窗口是隐藏的,则不用保存代码 # 如果代码窗口是隐藏的,则不用保存代码
return if myshixun.shixun.hide_code || myshixun.shixun.vnc return if myshixun.shixun.hide_code || myshixun.shixun.vnc
file_content = git_fle_content myshixun.repo_path, path file_content = git_fle_content myshixun.repo_path, path
unless file_content.present? #unless file_content.present?
raise("获取文件代码异常") # raise("获取文件代码异常")
end #end
logger.info("#######game_id:#{game_id}, file_content:#{file_content}") logger.info("#######game_id:#{game_id}, file_content:#{file_content}")
game_code = GameCode.where(:game_id => game_id, :path => path).first game_code = GameCode.where(:game_id => game_id, :path => path).first
if game_code.nil? if game_code.nil?
@ -621,22 +601,6 @@ class ApplicationController < ActionController::Base
cookies[:fileDownload] = true cookies[:fileDownload] = true
end end
def set_autologin_cookie(user)
token = Token.get_or_create_permanent_login_token(user, "autologin")
cookie_options = {
:value => token.value,
:expires => 1.month.from_now,
:path => '/',
:secure => false,
:httponly => true
}
if edu_setting('cookie_domain').present?
cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain'))
end
cookies[autologin_cookie_name] = cookie_options
logger.info("cookies is #{cookies}")
end
# 149课程的评审用户数据创建包含创建课堂学生 # 149课程的评审用户数据创建包含创建课堂学生
def open_class_user def open_class_user
user = User.find_by(login: "OpenClassUser") user = User.find_by(login: "OpenClassUser")

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

@ -0,0 +1,22 @@
class BindUsersController < ApplicationController
before_action :require_login
def create
user = CreateBindUserService.call(current_user, create_params)
successful_authentication(user) if user.id != current_user.id
render_ok
rescue ApplicationService::Error => ex
render_error(ex.message)
end
def new_user
current_user
end
private
def create_params
params.permit(:username, :password, :type, :not_bind)
end
end

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

@ -1,5 +1,6 @@
class Competitions::CompetitionsController < Competitions::BaseController class Competitions::CompetitionsController < Competitions::BaseController
skip_before_action :require_login skip_before_action :require_login
before_action :allow_visit, except: [:index]
def index def index
# 已上架 或者 即将上架 # 已上架 或者 即将上架
@ -24,10 +25,10 @@ class Competitions::CompetitionsController < Competitions::BaseController
end end
def show def show
unless current_competition.published? || admin_or_business?
render_forbidden
return
end end
def common_header
end end
private private
@ -35,4 +36,11 @@ class Competitions::CompetitionsController < Competitions::BaseController
def current_competition def current_competition
@_current_competition ||= Competition.find_by!(identifier: params[:id]) @_current_competition ||= Competition.find_by!(identifier: params[:id])
end end
def allow_visit
unless current_competition.published? || admin_or_business?
render_forbidden
return
end
end
end end

@ -1,39 +1,7 @@
module Admins::RenderHelper module Admins::RenderHelper
extend ActiveSupport::Concern extend ActiveSupport::Concern
def render_by_format(hash) include Base::RenderHelper
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
def render_delete_success def render_delete_success
render_js_template 'admins/shared/delete' render_js_template 'admins/shared/delete'

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

@ -1,4 +1,4 @@
module Admins::PaginateHelper module Base::PaginateHelper
extend ActiveSupport::Concern extend ActiveSupport::Concern
def offset def offset

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

@ -0,0 +1,69 @@
module LoginHelper
extend ActiveSupport::Concern
def edu_setting(name)
EduSetting.get(name)
end
def autologin_cookie_name
edu_setting('autologin_cookie_name').presence || 'autologin'
end
def set_autologin_cookie(user)
token = Token.get_or_create_permanent_login_token(user, "autologin")
cookie_options = {
:value => token.value,
:expires => 1.month.from_now,
:path => '/',
:secure => false,
:httponly => true
}
if edu_setting('cookie_domain').present?
cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain'))
end
cookies[autologin_cookie_name] = cookie_options
Rails.logger.info("cookies is #{cookies}")
end
def successful_authentication(user)
Rails.logger.info("id: #{user&.id} Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}")
# Valid user
self.logged_user = user
# generate a key and set cookie if autologin
set_autologin_cookie(user)
UserAction.create(action_id: user&.id, action_type: 'Login', user_id: user&.id, ip: request.remote_ip)
user.update_column(:last_login_on, Time.now)
# 注册完成后有一天的试用申请(先去掉)
# UserDayCertification.create(user_id: user.id, status: 1)
end
def logout_user
if User.current.logged?
if autologin = cookies.delete(autologin_cookie_name)
User.current.delete_autologin_token(autologin)
end
User.current.delete_session_token(session[:tk])
self.logged_user = nil
end
session[:user_id] = nil
end
# Sets the logged in user
def logged_user=(user)
reset_session
if user && user.is_a?(User)
User.current = user
start_user_session(user)
else
User.current = User.anonymous
end
end
def start_user_session(user)
session[:user_id] = user.id
session[:ctime] = Time.now.utc.to_i
session[:atime] = Time.now.utc.to_i
end
end

@ -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/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,23 @@
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
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,10 @@
class Cooperative::UsersController < Cooperative::BaseController
def index
params[:sort_by] = params[:sort_by].presence || 'created_on'
params[:sort_direction] = params[:sort_direction].presence || 'desc'
params[:school_id] = current_laboratory.school_id
users = Admins::UserQuery.call(params)
@users = paginate users.includes(user_extension: :school)
end
end

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

@ -2,7 +2,8 @@ class CourseModulesController < ApplicationController
before_action :require_login, :check_auth before_action :require_login, :check_auth
before_action :set_module, except: [:unhidden_modules] before_action :set_module, except: [:unhidden_modules]
before_action :find_course, only: [: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 def sticky_module
@ -48,9 +49,9 @@ class CourseModulesController < ApplicationController
tip_exception("已存在同名子目录") if @course_module.course_second_categories.exists?(name: params[:name].strip) tip_exception("已存在同名子目录") if @course_module.course_second_categories.exists?(name: params[:name].strip)
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
begin begin
@course_module.course_second_categories.create!(name: params[:name].strip, category_type: @course_module.module_type, category = @course_module.course_second_categories.create!(name: params[:name].strip, category_type: @course_module.module_type,
course_id: @course.id, position: @course_module.course_second_categories.count + 1) course_id: @course.id, position: @course_module.course_second_categories.count + 1)
normal_status(0, "添加成功") render :json => {category_id: category.id, status: 0, message: "添加成功"}
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
tip_exception("添加子目录失败") tip_exception("添加子目录失败")

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

@ -35,7 +35,7 @@ class CoursesController < ApplicationController
:transfer_to_course_group, :delete_from_course, :export_member_scores_excel, :transfer_to_course_group, :delete_from_course, :export_member_scores_excel,
:search_users, :add_students_by_search, :get_historical_courses, :add_teacher_popup, :search_users, :add_students_by_search, :get_historical_courses, :add_teacher_popup,
:add_teacher, :export_couser_info, :export_member_act_score, :add_teacher, :export_couser_info, :export_member_act_score,
:update_informs, :new_informs, :delete_informs] :update_informs, :new_informs, :delete_informs, :switch_to_student]
before_action :admin_allowed, only: [:set_invite_code_halt, :set_public_or_private, :change_course_admin, before_action :admin_allowed, only: [:set_invite_code_halt, :set_public_or_private, :change_course_admin,
:set_course_group, :create_group_by_importing_file, :set_course_group, :create_group_by_importing_file,
:update_task_position, :tasks_list] :update_task_position, :tasks_list]
@ -583,6 +583,8 @@ class CoursesController < ApplicationController
# 学生身份的处理 # 学生身份的处理
student_member = course_members.where(role: %i[STUDENT]).take student_member = course_members.where(role: %i[STUDENT]).take
# 不存在则创建学生身份
if params[:roles].include?("STUDENT") && student_member.blank? 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]) 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) new_student = CourseMember.new(user_id: params[:user_id], course_id: @course.id, role: 4)
@ -597,6 +599,9 @@ class CoursesController < ApplicationController
student_member.destroy! student_member.destroy!
CourseDeleteStudentDeleteWorksJob.perform_later(@course.id, [params[:user_id]]) CourseDeleteStudentDeleteWorksJob.perform_later(@course.id, [params[:user_id]])
# CourseDeleteStudentNotifyJob.perform_later(@course.id, [params[:user_id]], current_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 end
normal_status(0, "修改成功") normal_status(0, "修改成功")
@ -681,13 +686,19 @@ class CoursesController < ApplicationController
course_member = @course.course_members.find_by!(user_id: current_user.id, is_active: 1) course_member = @course.course_members.find_by!(user_id: current_user.id, is_active: 1)
tip_exception("切换失败") if course_member.STUDENT? tip_exception("切换失败") if course_member.STUDENT?
course_student = CourseMember.find_by!(user_id: current_user.id, role: %i[STUDENT], course_id: @course.id) course_student = CourseMember.find_by(user_id: current_user.id, role: %i[STUDENT], course_id: @course.id)
course_member.update_attributes(is_active: 0) course_member.update_attributes!(is_active: 0)
course_student.update_attributes(is_active: 1) if course_student
course_student.update_attributes!(is_active: 1)
else
# 学生身份不存在则创建
CourseMember.create!(user_id: current_user.id, role: 4, course_id: @course.id)
CourseAddStudentCreateWorksJob.perform_later(@course.id, [current_user.id])
end
normal_status(0, "切换成功") normal_status(0, "切换成功")
rescue => e rescue => e
uid_logger_error("switch_to_student error: #{e.message}") uid_logger_error("switch_to_student error: #{e.message}")
tip_exception("切换失败") tip_exception(e.message)
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
end end
@ -1127,7 +1138,7 @@ class CoursesController < ApplicationController
def top_banner def top_banner
@user = current_user @user = current_user
@is_teacher = @user_course_identity < Course::STUDENT @switch_student = Course::BUSINESS < @user_course_identity && @user_course_identity < Course::STUDENT
@is_student = @user_course_identity == Course::STUDENT @is_student = @user_course_identity == Course::STUDENT
@course.increment!(:visits) @course.increment!(:visits)
end end

@ -619,7 +619,7 @@ class ExerciseQuestionsController < ApplicationController
:status => 0 :status => 0
} }
ExerciseShixunAnswer.create(ex_shixun_option) ExerciseShixunAnswer.create(ex_shixun_option)
new_obj_score = @c_score new_obj_score = ex_obj_score + @c_score
end end
total_scores = new_obj_score + ex_subj_score total_scores = new_obj_score + ex_subj_score
if total_scores < 0.0 if total_scores < 0.0

@ -518,13 +518,13 @@ class ExercisesController < ApplicationController
common_group = exercise_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的 common_group = exercise_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的
new_group_ids = course_id - common_group #新传入的班级id new_group_ids = course_id - common_group #新传入的班级id
if common_group.count > 0 #判断试卷的分班设置是否存在,存在则更新,负责则新建 if common_group.count > 0 #判断试卷的分班设置是否存在,存在则更新,负责则新建
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 = { ex_group_params = {
:publish_time => exercise_publish_time, :publish_time => exercise_publish_time,
:end_time => exercise_end_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) the_group_setting_status = set_exercise_status(the_group_setting.publish_time,the_group_setting.end_time)
if the_group_setting_status == 2 if the_group_setting_status == 2
ex_group_params = { ex_group_params = {
@ -537,8 +537,8 @@ class ExercisesController < ApplicationController
:end_time => the_group_setting.end_time :end_time => the_group_setting.end_time
} }
end end
the_group_setting.update_attributes!(ex_group_params)
end end
exercise_group_sets.update_all(ex_group_params)
end end
if new_group_ids.size > 0 if new_group_ids.size > 0
new_group_ids.each do |c| new_group_ids.each do |c|
@ -560,8 +560,9 @@ class ExercisesController < ApplicationController
error_count == 0 error_count == 0
normal_status(-1,"已发布/已截止的试卷不允许修改时间") normal_status(-1,"已发布/已截止的试卷不允许修改时间")
else else
# 未发布的分班设置才能删除
if old_exercise_groups.size > 0 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 old_all_ex_groups.destroy_all
end end
#试卷更新为exercise_group_setting的发布时间最小截止时间最大 #试卷更新为exercise_group_setting的发布时间最小截止时间最大
@ -720,8 +721,8 @@ class ExercisesController < ApplicationController
if exercise.unified_setting if exercise.unified_setting
ex_status = exercise.exercise_status #则为试卷的状态 ex_status = exercise.exercise_status #则为试卷的状态
else else
ex_status = exercise.exercise_group_settings.find_in_exercise_group("course_group_id",params[:group_ids]) ex_status = @course.course_groups.where(id: params[:group_ids]).size !=
.exercise_group_not_published.present? ? 1 : 0 exercise.exercise_group_settings.where(course_group_id: params[:group_ids]).exercise_group_published.size ? 1 : 0
end end
if ex_status == 1 #如果试卷存在已发布的,或者是已截止的,那么则直接跳过 if ex_status == 1 #如果试卷存在已发布的,或者是已截止的,那么则直接跳过
g_course = group_ids #表示是否传入分班参数,如果传入分班的参数,那么试卷的统一设置需修改 g_course = group_ids #表示是否传入分班参数,如果传入分班的参数,那么试卷的统一设置需修改
@ -739,8 +740,8 @@ class ExercisesController < ApplicationController
g_course.each_with_index do |i, index| 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寻找试卷所在的班级 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 group_end_time = params[:detail] ? group_end_times[index] : ex_end_time
if exercise_group_setting #如果该试卷分组存在,则更新,否则新建 if exercise_group_setting.present? #如果该试卷分组存在,则更新,否则新建
exercise_group_setting.update_attributes(publish_time: Time.now, end_time: group_end_time) exercise_group_setting.update_attributes!(publish_time: Time.now, end_time: group_end_time)
else else
p_course_group = { p_course_group = {
:exercise_id => exercise.id, :exercise_id => exercise.id,
@ -1152,7 +1153,7 @@ class ExercisesController < ApplicationController
# 1 老师权限0 学生权限 # 1 老师权限0 学生权限
@is_teacher_or = (@user_course_identity < Course::STUDENT) ? 1 : 0 @is_teacher_or = (@user_course_identity < Course::STUDENT) ? 1 : 0
@student_status = 2 @student_status = 2
@exercise_questions = @exercise.exercise_questions.includes(:exercise_shixun_challenges,:exercise_standard_answers,:exercise_answers,:exercise_shixun_answers).order("question_number ASC") @exercise_questions = @exercise.exercise_questions.includes(:exercise_shixun_challenges,:exercise_standard_answers,:exercise_answers,:exercise_shixun_answers,:exercise_answer_comments).order("question_number ASC")
@question_status = [] @question_status = []
get_exercise_status = @exercise.get_exercise_status(current_user) #当前用户的试卷状态 get_exercise_status = @exercise.get_exercise_status(current_user) #当前用户的试卷状态
@ex_answer_status = @exercise.get_exercise_status(@ex_user&.user) #当前试卷用户的试卷状态 @ex_answer_status = @exercise.get_exercise_status(@ex_user&.user) #当前试卷用户的试卷状态
@ -1323,7 +1324,7 @@ class ExercisesController < ApplicationController
end end
rescue Exception => e rescue Exception => e
uid_logger_error(e.message) uid_logger_error(e.message)
tip_exception("页面调用失败!") tip_exception(e.message)
raise ActiveRecord::Rollback raise ActiveRecord::Rollback
end end
end end
@ -1425,18 +1426,24 @@ class ExercisesController < ApplicationController
@exercise_questions = @exercise.exercise_questions&.includes(:exercise_choices,:exercise_answers,:exercise_standard_answers,:exercise_shixun_challenges,:exercise_shixun_answers) @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? if params[:sort].present?
@paging_type = params[:sort].to_s percent_sort = params[:sort]
end 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) 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 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 end
@exercise_questions_count = @exercise_questions.size @exercise_questions_count = @exercise_questions.size
@ -1709,9 +1716,9 @@ class ExercisesController < ApplicationController
ques_number = q.question_number ques_number = q.question_number
end end
if q.question_type != Exercise::PRACTICAL if q.question_type != Exercise::PRACTICAL
ques_vote = q.exercise_answers.search_exercise_answer("user_id",user_id) ques_vote = q.exercise_answers.select{|answer| answer.user_id == user_id}
else else
ques_vote = q.exercise_shixun_answers.search_shixun_answers("user_id",user_id) ques_vote = q.exercise_shixun_answers.select{|answer| answer.user_id == user_id}
end end
ques_status = 0 ques_status = 0
if ques_vote.present? if ques_vote.present?

@ -9,19 +9,20 @@ class FilesController < ApplicationController
before_action :set_pagination, only: %i[index public_with_course_and_project mine_with_course_and_project] before_action :set_pagination, only: %i[index public_with_course_and_project mine_with_course_and_project]
before_action :validate_upload_params, only: %i[upload import] before_action :validate_upload_params, only: %i[upload import]
before_action :find_file, only: %i[show setting update] before_action :find_file, only: %i[show setting update]
before_action :publish_params, only: %i[upload import update]
SORT_TYPE = %w[created_on downloads quotes] SORT_TYPE = %w[created_on downloads quotes]
def index def index
sort = params[:sort] || 0 # 0: 降序1: 升序 sort = params[:sort] || 0 # 0: 降序1: 升序
sort_type = params[:sort_type] || 'created_on' # created_on时间排序 downloads下载次数排序; quotes: 引用次数排序 sort_type = params[:sort_type] || 'created_on' # created_on时间排序 downloads下载次数排序; quotes: 引用次数排序
course_second_category_id = params[:course_second_category_id] || 0 # 0: 为主目录, 其他为次目录id @course_second_category_id = params[:course_second_category_id] || 0 # 0: 为主目录, 其他为次目录id
@user = current_user @user = current_user
@attachments = course_second_category_id.to_i == 0 ? @course.attachments : @course.attachments.by_course_second_category_id(course_second_category_id) @attachments = @course_second_category_id.to_i == 0 ? @course.attachments.includes(:course_second_category) : @course.attachments.by_course_second_category_id(@course_second_category_id)
@attachments = @attachments.includes(attachment_group_settings: :course_group, author: [:user_extension, :course_members]) @attachments = @attachments.includes(author: [:user_extension, :course_members])
.ordered(sort: sort.to_i, sort_type: sort_type.strip) .ordered(sort: sort.to_i, sort_type: sort_type.strip)
get_category(@course, course_second_category_id) get_category(@course, @course_second_category_id)
@total_count = @attachments.size @total_count = @attachments.size
@publish_count = @attachments.published.size @publish_count = @attachments.published.size
@unpublish_count = @total_count - @publish_count @unpublish_count = @total_count - @publish_count
@ -137,9 +138,9 @@ class FilesController < ApplicationController
def upload def upload
attachment_ids = params[:attachment_ids] attachment_ids = params[:attachment_ids]
course_second_category_id = params[:course_second_category_id] || 0 # 0: 为主目录, 其他为次目录id course_second_category_id = params[:course_second_category_id] || 0 # 0: 为主目录, 其他为次目录id
is_unified_setting = params.has_key?(:is_unified_setting) ? params[:is_unified_setting] : true # is_unified_setting = params.has_key?(:is_unified_setting) ? params[:is_unified_setting] : true
publish_time = params[:publish_time] # publish_time = params[:publish_time]
course_group_publish_times = params[:course_group_publish_times] || [] # course_group_publish_times = params[:course_group_publish_times] || []
begin begin
attachment_ids.each do |attchment_id| attachment_ids.each do |attchment_id|
@ -148,9 +149,12 @@ class FilesController < ApplicationController
attachment.container = @course attachment.container = @course
attachment.course_second_category_id = course_second_category_id attachment.course_second_category_id = course_second_category_id
attachment.description = params[:description] attachment.description = params[:description]
attachment.is_public = params[:is_public] ? 1 : 0 attachment.is_public = params[:is_public] && @course.is_public == 1 ? 1 : 0
attachment.set_publish_time(publish_time) if is_unified_setting attachment.is_publish = @atta_is_publish
attachment.set_course_group_publish_time(@course, course_group_publish_times) if @course.course_groups.size > 0 && !is_unified_setting && publish_time.blank? attachment.delay_publish = @atta_delay_publish
attachment.publish_time = @atta_publish_time
# attachment.set_publish_time(publish_time) if is_unified_setting
# attachment.set_course_group_publish_time(@course, course_group_publish_times) if @course.course_groups.size > 0 && !is_unified_setting && publish_time.blank?
attachment.save! attachment.save!
end end
end end
@ -188,8 +192,9 @@ class FilesController < ApplicationController
attach_copied_obj.created_on = Time.now attach_copied_obj.created_on = Time.now
attach_copied_obj.author = current_user attach_copied_obj.author = current_user
attach_copied_obj.is_public = 0 attach_copied_obj.is_public = 0
attach_copied_obj.is_publish = 1 attach_copied_obj.is_publish = @atta_is_publish
attach_copied_obj.publish_time = Time.now attach_copied_obj.delay_publish = @atta_delay_publish
attach_copied_obj.publish_time = @atta_publish_time
attach_copied_obj.course_second_category_id = course_second_category_id attach_copied_obj.course_second_category_id = course_second_category_id
attach_copied_obj.copy_from = ori.copy_from.nil? ? ori.id : ori.copy_from attach_copied_obj.copy_from = ori.copy_from.nil? ? ori.id : ori.copy_from
if attach_copied_obj.attachtype == nil if attach_copied_obj.attachtype == nil
@ -209,11 +214,7 @@ class FilesController < ApplicationController
def update def update
return normal_status(403, "您没有权限进行该操作") if current_user.course_identity(@course) >= 5 && @file.author != current_user return normal_status(403, "您没有权限进行该操作") if current_user.course_identity(@course) >= 5 && @file.author != current_user
is_unified_setting = params[:is_unified_setting]
publish_time = params[:publish_time]
publish_time = format_time(Time.parse(publish_time)) unless publish_time.blank?
is_public = params[:is_public] is_public = params[:is_public]
course_group_publish_times = params[:course_group_publish_times] || []
@old_attachment = @file @old_attachment = @file
@new_attachment = Attachment.find_by_id params[:new_attachment_id] @new_attachment = Attachment.find_by_id params[:new_attachment_id]
@ -225,25 +226,29 @@ class FilesController < ApplicationController
old_course_second_category_id = @old_attachment.course_second_category_id old_course_second_category_id = @old_attachment.course_second_category_id
@old_attachment.copy_attributes_from_new_attachment(@new_attachment) @old_attachment.copy_attributes_from_new_attachment(@new_attachment)
@old_attachment.is_public = is_public == true ? 1 : 0 if is_public
@old_attachment.course_second_category_id = old_course_second_category_id @old_attachment.course_second_category_id = old_course_second_category_id
@old_attachment.save! @old_attachment.save!
@new_attachment.delete @new_attachment.delete
end end
@old_attachment.is_public = is_public == true && @course.is_public == 1 ? 1 : 0
@old_attachment.is_publish = @atta_is_publish
@old_attachment.delay_publish = @atta_delay_publish
@old_attachment.publish_time = @atta_publish_time
if params[:description] && !params[:description].strip.blank? && params[:description] != @old_attachment.description if params[:description] && !params[:description].strip.blank? && params[:description] != @old_attachment.description
@old_attachment.description = params[:description] @old_attachment.description = params[:description]
end end
@old_attachment.set_public(is_public) # @old_attachment.set_public(is_public)
if is_unified_setting # if is_unified_setting
@old_attachment.set_publish_time(publish_time) # @old_attachment.set_publish_time(publish_time)
@old_attachment.attachment_group_settings.destroy_all # @old_attachment.attachment_group_settings.destroy_all
end # end
if publish_time.blank? && @course.course_groups.size > 0 && !is_unified_setting # if publish_time.blank? && @course.course_groups.size > 0 && !is_unified_setting
@old_attachment.set_course_group_publish_time(@course, course_group_publish_times) # @old_attachment.set_course_group_publish_time(@course, course_group_publish_times)
end # end
@old_attachment.save! @old_attachment.save!
rescue Exception => e rescue Exception => e
@ -304,11 +309,19 @@ class FilesController < ApplicationController
end end
def file_validate_sort_type def file_validate_sort_type
normal_status(-2, "参数sort_tyope暂时只支持 'created_on', 'quotes', 'downloads'") if params.has_key?(:sort_type) && !SORT_TYPE.include?(params[:sort_type].strip) normal_status(-2, "参数sort_type暂时只支持 'created_on', 'quotes', 'downloads'") if params.has_key?(:sort_type) && !SORT_TYPE.include?(params[:sort_type].strip)
end end
def validate_upload_params def validate_upload_params
find_attachment_ids find_attachment_ids
find_course_second_category_id find_course_second_category_id
end end
def publish_params
tip_exception("缺少发布参数") if params[:delay_publish].blank?
tip_exception("缺少延期发布的时间参数") if params[:delay_publish].to_i == 1 && params[:publish_time].blank?
@atta_is_publish = params[:delay_publish].to_i == 1 && params[:publish_time].to_time > Time.now ? 0 : 1
@atta_delay_publish = params[:delay_publish].to_i
@atta_publish_time = params[:delay_publish].to_i == 1 && params[:publish_time] ? params[:publish_time] : Time.now
end
end end

@ -2,10 +2,12 @@ class HomeController < ApplicationController
def index def index
# banner图 # 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_url = []
images.each do |image| 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 end
# 目录分级 # 目录分级

@ -160,7 +160,8 @@ class HomeworkCommonsController < ApplicationController
# 作品状态 0 未提交, 1 按时提交, 2 延迟提交 # 作品状态 0 未提交, 1 按时提交, 2 延迟提交
if params[:work_status].present? if params[:work_status].present?
work_status = params[:work_status].map{|status| status.to_i} params_work_status = request.get? ? params[:work_status].split(",") : params[:work_status]
work_status = params_work_status.map{|status| status.to_i}
all_student_works = @student_works.left_joins(:myshixun) all_student_works = @student_works.left_joins(:myshixun)
@student_works = all_student_works.where(work_status: work_status) @student_works = all_student_works.where(work_status: work_status)
@ -170,7 +171,8 @@ class HomeworkCommonsController < ApplicationController
# 分班情况 # 分班情况
unless params[:course_group].blank? unless params[:course_group].blank?
group_user_ids = @course.students.where(course_group_id: params[:course_group]).pluck(:user_id) group_ids = request.get? ? params[:course_group].split(",") : params[:course_group]
group_user_ids = @course.students.where(course_group_id: group_ids).pluck(:user_id)
# 有分组只可能是老师身份查看列表 # 有分组只可能是老师身份查看列表
@student_works = @student_works.where(user_id: group_user_ids) @student_works = @student_works.where(user_id: group_user_ids)
end end
@ -482,7 +484,7 @@ class HomeworkCommonsController < ApplicationController
publish_time = setting[:publish_time] == "" ? Time.now : setting[:publish_time] publish_time = setting[:publish_time] == "" ? Time.now : setting[:publish_time]
# 截止时间为空时取发布时间后一个月 # 截止时间为空时取发布时间后一个月
end_time = setting[:end_time] == "" ? Time.at(publish_time.to_time.to_i+30*24*3600) : setting[:end_time] end_time = setting[:end_time]
HomeworkGroupSetting.where(homework_common_id: @homework.id, course_group_id: setting[:group_id]). HomeworkGroupSetting.where(homework_common_id: @homework.id, course_group_id: setting[:group_id]).
update_all(publish_time: publish_time, end_time: end_time) update_all(publish_time: publish_time, end_time: end_time)
setting_group_ids << setting[:group_id] setting_group_ids << setting[:group_id]
@ -1170,7 +1172,7 @@ class HomeworkCommonsController < ApplicationController
# 可立即截止的分班:统一设置则是用户管理的所有分班,否则是当前用户管理的分班中已发布且未截止的 # 可立即截止的分班:统一设置则是用户管理的所有分班,否则是当前用户管理的分班中已发布且未截止的
charge_group_ids = @course.charge_group_ids(@current_user) # 当前用户管理的分班 charge_group_ids = @course.charge_group_ids(@current_user) # 当前用户管理的分班
group_ids = @homework.unified_setting ? charge_group_ids : group_ids = @homework.unified_setting ? charge_group_ids :
@homework.homework_group_settings.where(course_group_id: charge_group_ids).none_end.pluck(:course_group_id) @homework.homework_group_settings.where(course_group_id: charge_group_ids).published_no_end.pluck(:course_group_id)
@course_groups = @course.course_groups.where(id: group_ids) @course_groups = @course.course_groups.where(id: group_ids)
else else
tip_exception("没有可截止的分班") tip_exception("没有可截止的分班")

@ -0,0 +1,20 @@
class Oauth::BaseController < ActionController::Base
include RenderHelper
include LoginHelper
skip_before_action :verify_authenticity_token
private
def session_user_id
session[:user_id]
end
def current_user
@_current_user ||= User.find_by(id: session_user_id)
end
def auth_hash
request.env['omniauth.auth']
end
end

@ -0,0 +1,9 @@
class Oauth::QQController < Oauth::BaseController
def create
user, new_user = Oauth::CreateOrFindQqAccountService.call(current_user, auth_hash)
successful_authentication(user)
render_ok(new_user: new_user)
end
end

@ -0,0 +1,11 @@
class Oauth::WechatController < Oauth::BaseController
def create
user, new_user = Oauth::CreateOrFindWechatAccountService.call(current_user ,params)
successful_authentication(user)
render_ok(new_user: new_user)
rescue Oauth::CreateOrFindWechatAccountService::Error => ex
render_error(ex.message)
end
end

@ -279,7 +279,8 @@ class PollsController < ApplicationController
if poll.unified_setting if poll.unified_setting
pl_status = poll.polls_status #则为试卷的状态 pl_status = poll.polls_status #则为试卷的状态
else 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 end
if pl_status == 1 #如果问卷存在已发布的,或者是已截止的,那么则直接跳过 if pl_status == 1 #如果问卷存在已发布的,或者是已截止的,那么则直接跳过
g_course = group_ids #表示是否传入分班参数,如果传入分班的参数那么poll的统一设置需修改 g_course = group_ids #表示是否传入分班参数,如果传入分班的参数那么poll的统一设置需修改
@ -295,8 +296,8 @@ class PollsController < ApplicationController
g_course.each_with_index do |i, index| 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寻找问卷所在的班级 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 group_end_time = params[:detail] ? group_end_times[index] : ex_end_time
if poll_group_setting #如果该问卷分组存在,则更新,否则新建 if poll_group_setting.present? #如果该问卷分组存在,则更新,否则新建
poll_group_setting.update_attributes(publish_time: Time.now, end_time: group_end_time) poll_group_setting.update_attributes!(publish_time: Time.now, end_time: group_end_time)
else else
p_course_group = { p_course_group = {
:poll_id => poll.id, :poll_id => poll.id,
@ -306,7 +307,7 @@ class PollsController < ApplicationController
:end_time => group_end_time, :end_time => group_end_time,
} }
new_poll_group = poll.poll_group_settings.new p_course_group new_poll_group = poll.poll_group_settings.new p_course_group
new_poll_group.save new_poll_group.save!
end end
end end
e_time = poll.poll_group_settings.end_time_present.map(&:end_time).max e_time = poll.poll_group_settings.end_time_present.map(&:end_time).max
@ -782,13 +783,13 @@ class PollsController < ApplicationController
common_group = poll_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的 common_group = poll_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的
new_group_ids = course_id - common_group #新传入的班级id new_group_ids = course_id - common_group #新传入的班级id
if common_group.size > 0 #传入的参数存在已发布的分班 if common_group.size > 0 #传入的参数存在已发布的分班
poll_group = poll_groups.find_in_poll_group("course_group_id",common_group)
poll_group.each do |the_group_setting|
poll_group_params = { poll_group_params = {
:publish_time => poll_publish_time, :publish_time => poll_publish_time,
:end_time => poll_end_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) the_group_setting_status = set_poll_status(the_group_setting.publish_time,the_group_setting.end_time)
if the_group_setting_status == 2 if the_group_setting_status == 2
poll_group_params = { poll_group_params = {
@ -801,8 +802,8 @@ class PollsController < ApplicationController
:end_time => the_group_setting.end_time :end_time => the_group_setting.end_time
} }
end end
the_group_setting.update_attributes(poll_group_params)
end end
poll_group.update_all(poll_group_params)
end end
if new_group_ids.size > 0 if new_group_ids.size > 0
new_group_ids.each do |c| new_group_ids.each do |c|
@ -824,8 +825,9 @@ class PollsController < ApplicationController
error_count == 0 error_count == 0
normal_status(-1,"已发布/已截止的问卷不允许修改时间") normal_status(-1,"已发布/已截止的问卷不允许修改时间")
else else
# 未发布的分班设置才能删除
if old_poll_groups.size > 0 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 old_all_poll_groups.destroy_all
end end
#问卷更新为poll_group_setting的发布时间最小截止时间最大 #问卷更新为poll_group_setting的发布时间最小截止时间最大

@ -91,12 +91,16 @@ class QuestionBanksController < ApplicationController
banks = @object_type.classify.constantize.where(id: params[:object_id]) banks = @object_type.classify.constantize.where(id: params[:object_id])
course = current_user.manage_courses.find_by!(id: params[:course_id]) course = current_user.manage_courses.find_by!(id: params[:course_id])
task_ids = [] task_ids = []
homework_type = ""
container_type = ""
banks.each do |bank| banks.each do |bank|
case @object_type case @object_type
when 'HomeworkBank' # 作业 when 'HomeworkBank' # 作业
task = quote_homework_bank bank, course task = quote_homework_bank bank, course
homework_type = task.homework_type
when 'ExerciseBank' when 'ExerciseBank'
if bank.container_type == 'Exercise' # 试卷 container_type = bank.container_type
if container_type == 'Exercise' # 试卷
task = quote_exercise_bank bank, course task = quote_exercise_bank bank, course
else # 问卷 else # 问卷
task = quote_poll_bank bank, course task = quote_poll_bank bank, course
@ -108,7 +112,23 @@ class QuestionBanksController < ApplicationController
end end
task_ids << task.id if task task_ids << task.id if task
end end
render :json => {task_ids: task_ids, status: 0, message: "发送成功"}
case @object_type
when 'HomeworkBank' # 作业
category_id = course.course_modules.find_by(module_type: homework_type == "normal" ? "common_homework" : "group_homework")&.id
when 'ExerciseBank'
if container_type == 'Exercise' # 试卷
category_id = course.course_modules.find_by(module_type: "exercise")&.id
else # 问卷
category_id = course.course_modules.find_by(module_type: "poll")&.id
end
when 'GtaskBank'
category_id = course.course_modules.find_by(module_type: "graduation")&.id
when 'GtopicBank'
category_id = course.course_modules.find_by(module_type: "graduation")&.id
end
render :json => {task_ids: task_ids, category_id: category_id, status: 0, message: "发送成功"}
end end
def destroy def destroy

@ -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,34 @@
class Weapps::BaseController < ApplicationController
private
def require_wechat_login!
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)
session[:openid] = openid
end
def session_unionid
session[:unionid]
end
def set_session_unionid(unionid)
session[:unionid] = unionid
end
end

@ -0,0 +1,26 @@
class Weapps::CodeSessionsController < Weapps::BaseController
def create
logged = false
return render_error('code不能为空') if params[:code].blank?
result = Wechat::Weapp.jscode2session(params[:code])
set_session_openid(result['openid'])
set_weapp_session_key(result['session_key']) # weapp session_key写入缓存 后续解密需要
# 已授权,绑定过账号
open_user = OpenUsers::Wechat.find_by(uid: result['unionid'])
if open_user.present? && open_user.user
set_session_unionid(result['unionid'])
successful_authentication(open_user.user)
logged = true
else
# 新用户
user_info = Wechat::Weapp.decrypt(result['session_key'], params[:encrypted_data], params[:iv])
set_session_unionid(user_info['unionId'])
end
render_ok(openid: result['openid'], logged: logged)
end
end

@ -0,0 +1,12 @@
class Weapps::HomesController < Weapps::BaseController
def show
# banner图
@images = PortalImage.where(status: true).order(position: :asc)
# 热门实训
@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

@ -0,0 +1,24 @@
class Weapps::SessionsController < Weapps::BaseController
before_action :require_wechat_login!
def create
return render_error('重复登录') if current_user.present? && current_user.logged?
user = User.try_to_login(params[:login], params[:password])
return render_error('错误的账号或密码') if user.blank?
return render_error('违反平台使用规范,账号已被锁定') if user.locked?
return render_error('错误的账号或密码') unless user.check_password?(params[:password].to_s)
if user.wechat_open_user && user.wechat_open_user.uid != session_unionid
render_error('该账号已被其它微信号绑定')
return
end
# 绑定微信号
OpenUsers::Wechat.create!(user: user, uid: session_unionid) if user.wechat_open_user.blank?
successful_authentication(user)
render_ok
end
end

@ -0,0 +1,8 @@
class Weapps::VerifiesController < Weapps::BaseController
before_action :require_wechat_login!
def create
valid = Wechat::Weapp.verify?(session_openid, params[:verify_string], params[:signature])
render_ok(valid: valid)
end
end

@ -4,5 +4,4 @@ class Users::UpdatePasswordForm
attr_accessor :password, :old_password attr_accessor :password, :old_password
validates :password, presence: true validates :password, presence: true
validates :old_password, presence: true
end end

@ -1,110 +1,3 @@
module Admins::BaseHelper module Admins::BaseHelper
def sidebar_item_group(url, text, **opts) include ManageBackHelper
link_opts = url.start_with?('/') ? {} : { 'data-toggle': 'collapse', 'aria-expanded': false }
content =
link_to url, link_opts do
content_tag(:i, '', class: "fa fa-#{opts[:icon]}", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) +
content_tag(:span, text)
end
content +=
content_tag(:ul, id: url[1..-1], class: 'collapse list-unstyled', "data-parent": '#sidebar') do
yield
end
raw content
end
def sidebar_item(url, text, **opts)
content =
link_to url, 'data-controller': opts[:controller] do
content_tag(:i, '', class: "fa fa-#{opts[:icon]} fa-fw", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) +
content_tag(:span, text)
end
raw content
end
def admin_sidebar_controller
key = params[:controller].to_s.gsub(/\//, '-')
SidebarUtil.controller_name(key) || key
end
def define_admin_breadcrumbs(&block)
content_for(:setup_admin_breadcrumb, &block)
end
def add_admin_breadcrumb(text, url = nil)
@_admin_breadcrumbs ||= []
@_admin_breadcrumbs << OpenStruct.new(text: text, url: url)
end
def display_text(str, default = '--')
str.presence || default
end
def overflow_hidden_span(text, width: 300)
opts = { class: 'd-inline-block text-truncate', style: "max-width: #{width}px" }
opts.merge!('data-toggle': 'tooltip', title: text) if text != '--'
content_tag(:span, text, opts)
end
def sort_tag(content = '', **opts)
options = {}
options[:sort_by] = opts.delete(:name)
is_current_sort = params[:sort_by].to_s == options[:sort_by]
options[:sort_direction] = is_current_sort && params[:sort_direction].to_s == 'desc' ? 'asc' : 'desc'
path = opts.delete(:path) + "?" + unsafe_params.merge(options).to_query
arrow_class = case params[:sort_direction].to_s
when 'desc' then 'fa-sort-amount-desc'
when 'asc' then 'fa-sort-amount-asc'
else ''
end
opts[:style] = "#{opts[:style]} ;position: relative;"
content_tag(:span, opts) do
link_to path, remote: true do
content = content_tag(:span) { yield } if block_given?
content += content_tag(:i, '', class: "fa color-light-green ml-1 #{arrow_class}", style: 'position: absolute;top:0;') if is_current_sort
raw content
end
end
end
def javascript_void_link(name, **opts)
raw link_to(name, 'javascript:void(0)', opts)
end
def agree_link(name, url, **opts)
klass = ['action agree-action', opts.delete(:class)].compact.join(' ')
refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}"
url = url + (url.index('?') ? '&' : '?') + refresh_url_data
raw link_to(name, url, { method: :post, remote: true, class: klass, 'data-confirm': '确认审核通过?'}.merge(opts))
end
def delete_link(name, url, **opts, &block)
klass = ['action delete-action', opts.delete(:class)].compact.join(' ')
refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}"
url = url + (url.index('?') ? '&' : '?') + refresh_url_data
if block_given?
raw link_to(url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts), &block)
else
raw link_to(name, url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts))
end
end
def unsafe_params
params.except(:controller, :action).to_unsafe_h
end
def list_index_no(page,index)
(page - 1) * 20 + index + 1
end
end end

@ -0,0 +1,3 @@
module Cooperative::BaseHelper
include ManageBackHelper
end

@ -62,7 +62,7 @@ module CoursesHelper
course_board = course.course_board course_board = course.course_board
"/courses/#{course.id}/boards/#{course_board.id}" "/courses/#{course.id}/boards/#{course_board.id}"
when "course_group" when "course_group"
"/courses/#{course.id}/students" "/courses/#{course.id}/course_groups"
end end
end end

@ -10,9 +10,9 @@ module ExercisesHelper
exercise_obj_status.each do |q| exercise_obj_status.each do |q|
q_type = q.question_type q_type = q.question_type
if q_type == Exercise::PRACTICAL if q_type == Exercise::PRACTICAL
answers_content = q.exercise_shixun_answers.search_shixun_answers("user_id",user_id) answers_content = q.exercise_shixun_answers.select{|answer| answer.user_id == user_id}
else else
answers_content = q.exercise_answers.search_answer_users("user_id",user_id) answers_content = q.exercise_answers.select{|answer| answer.user_id == user_id}
end end
if q_type <= Exercise::JUDGMENT if q_type <= Exercise::JUDGMENT
@ -39,8 +39,13 @@ module ExercisesHelper
else else
ques_score = 0.0 ques_score = 0.0
end end
elsif q_type == Exercise::COMPLETION
ques_score = answers_content.select{|answer| answer.score >= 0.0}.pluck(:score).sum
if ques_score.to_s.split(".").last == "9"
ques_score = ques_score.to_f + 0.1
end
else else
ques_score = answers_content.score_reviewed.select(:score).pluck(:score).sum ques_score = answers_content.select{|answer| answer.score >= 0.0}.pluck(:score).sum
end end
if ques_score >= q.question_score #满分作答为正确 if ques_score >= q.question_score #满分作答为正确
@ -64,7 +69,7 @@ module ExercisesHelper
exercise_sub_status = exercise_questions.find_by_custom("question_type",Exercise::SUBJECTIVE) #主观题 exercise_sub_status = exercise_questions.find_by_custom("question_type",Exercise::SUBJECTIVE) #主观题
@ex_sub_array = [] #主观题的已答/未答 @ex_sub_array = [] #主观题的已答/未答
exercise_sub_status.each do |s| exercise_sub_status.each do |s|
sub_answer = s.exercise_answers.search_answer_users("user_id",user_id) #主观题只有一个回答 sub_answer = s.exercise_answers.select{|answer| answer.user_id == user_id} #主观题只有一个回答
if sub_answer.present? && sub_answer.first.score >= 0.0 if sub_answer.present? && sub_answer.first.score >= 0.0
if s.question_score <= sub_answer.first.score if s.question_score <= sub_answer.first.score
stand_status = 1 stand_status = 1
@ -115,14 +120,27 @@ module ExercisesHelper
if ex.question_type > Exercise::COMPLETION #当为主观题和实训题时, if ex.question_type > Exercise::COMPLETION #当为主观题和实训题时,
ex_answered_scores = effictive_users.score_reviewed.pluck(:score).sum #该问题的全部得分 ex_answered_scores = effictive_users.score_reviewed.pluck(:score).sum #该问题的全部得分
percent = (ex_total_score == 0.0 ? 0.0 : (ex_answered_scores / ex_total_score.to_f).round(3) * 100) #正确率 percent = (ex_total_score == 0.0 ? 0.0 : (ex_answered_scores / ex_total_score.to_f).round(3) * 100) #正确率
end end
# if ex.question_type != Exercise::MULTIPLE
# ex_answered_scores = effictive_users.score_reviewed.pluck(:score).sum #该问题的全部得分
# percent = (ex_total_score == 0.0 ? 0.0 : (ex_answered_scores / ex_total_score.to_f).round(3) * 100) #正确率
# else
# multiple_score = 0
# user_ids.each do |user_id|
# ex_answer_score = ex_answers.select{|answer| answer.user_id == user_id}&.first&.score.to_f
# multiple_score += ex_answer_score
# end
# percent = (ex_total_score == 0.0 ? 0.0 : (multiple_score / ex_total_score.to_f).round(3) * 100) #正确率
# end
question_answer_infos = [] question_answer_infos = []
if ex.question_type <= Exercise::JUDGMENT #选择题和判断题 if ex.question_type <= Exercise::JUDGMENT #选择题和判断题
ex_choices = ex.exercise_choices ex_choices = ex.exercise_choices
standard_answer = ex.exercise_standard_answers.pluck(:exercise_choice_id).sort #标准答案的位置 standard_answer = ex.exercise_standard_answers.pluck(:exercise_choice_id).sort #标准答案的位置
# right_users_count = 0 right_users_count = 0
# 该问题的正确率 #该问题的正确率
if ex.question_type == Exercise::MULTIPLE #多选题 if ex.question_type == Exercise::MULTIPLE #多选题
right_user_ids = user_ids right_user_ids = user_ids
standard_answer.each do |choice_position| standard_answer.each do |choice_position|
@ -130,10 +148,13 @@ module ExercisesHelper
right_user_ids = right_user_ids & effictive_users.select{|answer| answer.exercise_choice_id == standard_answer_choice_id}.pluck(:user_id) right_user_ids = right_user_ids & effictive_users.select{|answer| answer.exercise_choice_id == standard_answer_choice_id}.pluck(:user_id)
end end
right_users_count = right_user_ids.size right_users_count = right_user_ids.size
# right_users_scores = right_users_count * ex&.question_score.to_f
else #单选题和判断题 else #单选题和判断题
standard_answer_choice_id = ex_choices.select{|ec| ec.choice_position == standard_answer.first}.first&.id standard_answer_choice_id = ex_choices.select{|ec| ec.choice_position == standard_answer.first}.first&.id
right_users_count = effictive_users.select{|answer| answer.exercise_choice_id == standard_answer_choice_id}.size right_users_count = effictive_users.select{|answer| answer.exercise_choice_id == standard_answer_choice_id}.size
# right_users_scores = right_users_count * ex&.question_score.to_f
end end
# percent = (ex_total_score == 0.0 ? 0.0 : (right_users_scores / ex_total_score.to_f).round(3) * 100) #正确率
percent = commit_user_ids > 0 ? (right_users_count / commit_user_ids.to_f).round(3)*100 : 0.0 percent = commit_user_ids > 0 ? (right_users_count / commit_user_ids.to_f).round(3)*100 : 0.0
@ -159,6 +180,7 @@ module ExercisesHelper
null_stand_choice = null_standard_answer.pluck(:exercise_choice_id) #一个exercise_choice_id可能对应多个answer_text null_stand_choice = null_standard_answer.pluck(:exercise_choice_id) #一个exercise_choice_id可能对应多个answer_text
null_stand_text = null_standard_answer.pluck(:answer_text) null_stand_text = null_standard_answer.pluck(:answer_text)
standard_answer_count = 0 standard_answer_count = 0
each_null_score = null_stand_choice.size > 0 ? (ex&.question_score.to_f / null_stand_choice.uniq.size).round(3) : 0.0
all_user_count = 0 all_user_count = 0
null_stand_choice.each_with_index do |s,index| null_stand_choice.each_with_index do |s,index|
user_count = 0 user_count = 0
@ -181,7 +203,10 @@ module ExercisesHelper
all_user_count += user_count all_user_count += user_count
standard_answer_count += 1 standard_answer_count += 1
end end
percent = commit_user_ids > 0 ? (all_user_count / commit_user_ids.to_f).round(3)*100 : 0.0 answer_user_score = all_user_count * each_null_score
percent = (ex_total_score == 0.0 ? 0.0 : (answer_user_score / ex_total_score.to_f).round(3) * 100) #正确率
# percent = commit_user_ids > 0 ? (all_user_count / commit_user_ids.to_f).round(3)*100 : 0.0
user_wrong_count = (effictive_users_count - all_user_count ) user_wrong_count = (effictive_users_count - all_user_count )
@ -772,16 +797,24 @@ module ExercisesHelper
question_comment = [] question_comment = []
# user_score_pre = nil # user_score_pre = nil
if ques_type == 5 if ques_type == 5
exercise_answers = q.exercise_shixun_answers.search_shixun_answers("user_id",ex_answerer_id) exercise_answers = q.exercise_shixun_answers.select{|answer| answer.user_id == ex_answerer_id}
else else
exercise_answers = q.exercise_answers.search_exercise_answer("user_id",ex_answerer_id) #试卷用户的回答 exercise_answers = q.exercise_answers.select{|answer| answer.user_id == ex_answerer_id} #试卷用户的回答
end end
if student_status == 2 #当前为老师,或为学生且已提交 if student_status == 2 #当前为老师,或为学生且已提交
user_score_pre = exercise_answers.score_reviewed user_score_pre = exercise_answers.select{|answer| answer.score >= 0.0}
if ques_type == 4 #主观题时且没有大于0的分数时为空 if ques_type == 4 #主观题时且没有大于0的分数时为空
user_score = user_score_pre.present? ? user_score_pre.pluck(:score).sum : nil user_score = user_score_pre.present? ? user_score_pre.pluck(:score).sum : nil
elsif ques_type == 5 || ques_type == 3 elsif ques_type == 5
user_score = user_score_pre.present? ? user_score_pre.pluck(:score).sum : 0.0
elsif ques_type == 3 #填空题时需小心出现9.9分
user_score = user_score_pre.present? ? user_score_pre.pluck(:score).sum : 0.0 user_score = user_score_pre.present? ? user_score_pre.pluck(:score).sum : 0.0
if user_score > 0.0
if user_score.to_s.split(".").last == "9"
user_score = user_score.to_f + 0.1
end
end
# user_score = user_score_pre.present? ? user_score_pre.pluck(:score).sum : 0.0
else #选择题,判断题根据第一个记录查分 else #选择题,判断题根据第一个记录查分
user_score = user_score_pre.present? ? user_score_pre.first.score : 0.0 user_score = user_score_pre.present? ? user_score_pre.first.score : 0.0
@ -829,7 +862,7 @@ module ExercisesHelper
if ex_type == 4 #填空题/主观题/实训题有评论的 if ex_type == 4 #填空题/主观题/实训题有评论的
q_answer_id = exercise_answers.present? ? exercise_answers.first.id : nil q_answer_id = exercise_answers.present? ? exercise_answers.first.id : nil
question_comment = q.exercise_answer_comments.search_answer_comments("exercise_answer_id",q_answer_id) question_comment = q.exercise_answer_comments.select{|comment| comment.exercise_answer_id == q_answer_id}
end end
{ {
"user_score": (user_score.present? ? user_score.round(1).to_s : nil), "user_score": (user_score.present? ? user_score.round(1).to_s : nil),

@ -286,10 +286,18 @@ module ExportHelper
@user_columns = [] @user_columns = []
ques_type_boolean = question_types.include?(4) ques_type_boolean = question_types.include?(4)
if ques_type_boolean #仅存在主观题或客观题的时候 if ques_type_boolean #仅存在主观题或客观题的时候
@table_columns = @table_columns + %w(客观题成绩 主观题成绩 最终成绩 开始答题时间 提交时间) @table_columns = @table_columns + %w(客观题成绩 主观题成绩 最终成绩)
else else
@table_columns = @table_columns + %w(最终成绩 开始答题时间 提交时间) @table_columns = @table_columns + %w(最终成绩)
end end
for i in 1 .. exercise.exercise_questions.size
@table_columns = @table_columns + ["#{i}"]
end
@table_columns = @table_columns + %w(开始答题时间 提交时间)
questions = exercise.exercise_questions.includes(:exercise_answers,:exercise_shixun_answers).order("question_number ASC")
export_ex_users.includes(user: :user_extension).each_with_index do |e_user,index| export_ex_users.includes(user: :user_extension).each_with_index do |e_user,index|
user_info = e_user.user user_info = e_user.user
member = course.students.find_by_user_id(e_user.user_id) member = course.students.find_by_user_id(e_user.user_id)
@ -312,11 +320,36 @@ module ExportHelper
user_option = [index+1,user_login,user_real_name, user_mail, user_option = [index+1,user_login,user_real_name, user_mail,
user_student_id,user_course,user_commit_stu] user_student_id,user_course,user_commit_stu]
if ques_type_boolean if ques_type_boolean
other_user_option = [user_obj_score,user_suj_score,user_score,user_start_time,user_end_time] other_user_option = [user_obj_score,user_suj_score,user_score]
else
other_user_option = [user_score]
end
time_option = [user_start_time,user_end_time]
score_option = []
questions.each do |q|
q_type = q.question_type
if q_type == Exercise::PRACTICAL
answers_content = q.exercise_shixun_answers.select{|answer| answer.user_id == e_user.user_id}
else else
other_user_option = [user_score,user_start_time,user_end_time] answers_content = q.exercise_answers.select{|answer| answer.user_id == e_user.user_id}
end end
user_option = user_option + other_user_option
if q_type <= Exercise::JUDGMENT || q_type == Exercise::SUBJECTIVE
if answers_content.present? #学生有回答时,分数已经全部存到exercise_answer 表,所以可以直接取第一个值
ques_score = answers_content.first.score
ques_score = ques_score.nil? || ques_score < 0 ? 0.0 : ques_score
else
ques_score = 0.0
end
else
ques_score = answers_content.select{|answer| answer.score >= 0.0}.pluck(:score).sum
end
score_option << ques_score
end
user_option = user_option + other_user_option + score_option + time_option
@user_columns.push(user_option) @user_columns.push(user_option)
end end
end end
@ -419,7 +452,7 @@ module ExportHelper
end end
end end
out_file_name = "作品附件_#{homework_common&.course&.name}_#{homework_common.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip" out_file_name = "作品附件_#{homework_common.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
out_file_name.gsub!(" ", "-") out_file_name.gsub!(" ", "-")
out_file_name.gsub!("/", "_") out_file_name.gsub!("/", "_")
out_file = find_or_pack(homework_common, homework_common.user_id, digests.sort){ out_file = find_or_pack(homework_common, homework_common.user_id, digests.sort){

@ -0,0 +1,115 @@
module ManageBackHelper
extend ActiveSupport::Concern
def sidebar_item_group(url, text, **opts)
link_opts = url.start_with?('/') ? {} : { 'data-toggle': 'collapse', 'aria-expanded': false }
content =
link_to url, link_opts do
content_tag(:i, '', class: "fa fa-#{opts[:icon]}", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) +
content_tag(:span, text)
end
content +=
content_tag(:ul, id: url[1..-1], class: 'collapse list-unstyled', "data-parent": '#sidebar') do
yield
end
raw content
end
def sidebar_item(url, text, **opts)
content =
link_to url, 'data-controller': opts[:controller] do
content_tag(:i, '', class: "fa fa-#{opts[:icon]} fa-fw", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) +
content_tag(:span, text)
end
raw content
end
def admin_sidebar_controller
key = params[:controller].to_s.gsub(/\//, '-')
SidebarUtil.controller_name(key) || key
end
alias_method :sidebar_controller, :admin_sidebar_controller
def define_admin_breadcrumbs(&block)
content_for(:setup_admin_breadcrumb, &block)
end
alias_method :define_breadcrumbs, :define_admin_breadcrumbs
def add_admin_breadcrumb(text, url = nil)
@_breadcrumbs ||= []
@_breadcrumbs << OpenStruct.new(text: text, url: url)
end
alias_method :add_breadcrumb, :add_admin_breadcrumb
def display_text(str, default = '--')
str.presence || default
end
def overflow_hidden_span(text, width: 300)
opts = { class: 'd-inline-block text-truncate', style: "max-width: #{width}px" }
opts.merge!('data-toggle': 'tooltip', title: text) if text != '--'
content_tag(:span, text, opts)
end
def sort_tag(content = '', **opts)
options = {}
options[:sort_by] = opts.delete(:name)
is_current_sort = params[:sort_by].to_s == options[:sort_by]
options[:sort_direction] = is_current_sort && params[:sort_direction].to_s == 'desc' ? 'asc' : 'desc'
path = opts.delete(:path) + "?" + unsafe_params.merge(options).to_query
arrow_class = case params[:sort_direction].to_s
when 'desc' then 'fa-sort-amount-desc'
when 'asc' then 'fa-sort-amount-asc'
else ''
end
opts[:style] = "#{opts[:style]} ;position: relative;"
content_tag(:span, opts) do
link_to path, remote: true do
content = content_tag(:span) { yield } if block_given?
content += content_tag(:i, '', class: "fa color-light-green ml-1 #{arrow_class}", style: 'position: absolute;top:0;') if is_current_sort
raw content
end
end
end
def javascript_void_link(name, **opts)
raw link_to(name, 'javascript:void(0)', opts)
end
def agree_link(name, url, **opts)
klass = ['action agree-action', opts.delete(:class)].compact.join(' ')
refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}"
url = url + (url.index('?') ? '&' : '?') + refresh_url_data
raw link_to(name, url, { method: :post, remote: true, class: klass, 'data-confirm': '确认审核通过?'}.merge(opts))
end
def delete_link(name, url, **opts, &block)
klass = ['action delete-action', opts.delete(:class)].compact.join(' ')
refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}"
url = url + (url.index('?') ? '&' : '?') + refresh_url_data
if block_given?
raw link_to(url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts), &block)
else
raw link_to(name, url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts))
end
end
def unsafe_params
params.except(:controller, :action).to_unsafe_h
end
def list_index_no(page,index)
(page - 1) * 20 + index + 1
end
end

@ -0,0 +1,23 @@
# 基于Redis实现热门搜索关键字
class HotSearchKeyword
class << self
def add(keyword)
return if keyword.blank?
Rails.cache.data.zincrby(redis_key, 1, keyword)
end
def hot(limit = 5)
Rails.cache.data.zrevrange(redis_key, 0, limit - 1)
end
def available?
Rails.cache.is_a?(ActiveSupport::Cache::RedisStore)
end
private
def redis_key
'educoder:es:hot_keyword'
end
end
end

@ -0,0 +1,50 @@
module OmniAuth
module Strategies
class QQ < OmniAuth::Strategies::OAuth2
option :client_options, {
site: 'https://graph.qq.com',
authorize_url: '/oauth2.0/authorize',
token_url: '/oauth2.0/token'
}
def request_phase
super
end
def authorize_params
super.tap do |params|
%w[scope client_options].each do |v|
if request.params[v]
params[v.to_sym] = request.params[v]
end
end
end
end
uid { raw_info['openid'].to_s }
info do
{
name: user_info['nickname'],
nickname: user_info['nickname'],
image: user_info['figureurl_qq_1']
}
end
extra do
{ raw_info: user_info }
end
def raw_info
access_token.options[:mode] = :query
@raw_info ||= access_token.get('/oauth2.0/me').parsed
end
def user_info
access_token.options[:mode] = :query
params = { oauth_consumer_key: options.client_id, openid: raw_info['openid'], format: 'json' }
@user_info ||= access_token.get('/user/get_user_info', params: params)
end
end
end
end

@ -1,3 +1,5 @@
require 'open-uri'
module Util module Util
module_function module_function
@ -29,6 +31,16 @@ module Util
end end
end end
def download_file(url, save_path)
data = open(url, &:read)
file = File.new(save_path, 'w+')
file.binmode
file << data
file.flush
file.close
file
end
def logger_error(exception) def logger_error(exception)
Rails.logger.error(exception.message) Rails.logger.error(exception.message)
exception.backtrace.each { |message| Rails.logger.error(message) } exception.backtrace.each { |message| Rails.logger.error(message) }

@ -1,11 +0,0 @@
class Wechat::App
class << self
attr_accessor :appid, :secret
delegate :access_token, :jscode2session, to: :client
def client
@_client ||= Wechat::Client.new(appid, secret)
end
end
end

@ -0,0 +1,47 @@
class Wechat::Weapp
class << self
attr_accessor :appid, :secret
delegate :access_token, :jscode2session, to: :client
def client
@_client ||= Wechat::Client.new(appid, secret)
end
def session_key(openid)
Rails.cache.read(session_key_cache_key(openid))
end
def write_session_key(openid, session_key)
Rails.cache.write(session_key_cache_key(openid), session_key)
end
def verify?(openid, str, signature)
session_key = session_key(openid)
Digest::SHA1.hexdigest("#{str}#{session_key}") == signature
end
def decrypt(session_key, encrypted_data, iv)
session_key = Base64.decode64(session_key)
encrypted_data = Base64.decode64(encrypted_data)
iv = Base64.decode64(iv)
cipher = OpenSSL::Cipher::AES.new(128, :CBC)
cipher.decrypt
cipher.padding = 0
cipher.key = session_key
cipher.iv = iv
data = cipher.update(encrypted_data) << cipher.final
result = JSON.parse(data[0...-data.last.ord])
raise Wechat::Error, '解密错误' if result.dig('watermark', 'appid') != appid
result
end
private
def session_key_cache_key(openid)
"weapp:#{appid}:#{openid}:session_key"
end
end
end

@ -0,0 +1,13 @@
module WechatOauth
class << self
attr_accessor :appid, :secret, :scope, :base_url
def logger
@_logger ||= STDOUT
end
def logger=(l)
@_logger = l
end
end
end

@ -0,0 +1,14 @@
class WechatOauth::Error < StandardError
attr_reader :code
def initialize(code, msg)
super(msg)
@code = code
end
def message
I18n.t("oauth.wechat.#{code}")
rescue I18n::MissingTranslationData
super
end
end

@ -0,0 +1,61 @@
module WechatOauth::Service
module_function
def request(method, url, params)
WechatOauth.logger.info("[WechatOauth] [#{method.to_s.upcase}] #{url} || #{params}")
client = Faraday.new(url: WechatOauth.base_url)
response = client.public_send(method, url, params)
result = JSON.parse(response.body)
WechatOauth.logger.info("[WechatOauth] [#{response.status}] #{result}")
if result['errcode'].present? && result['errcode'].to_s != '0'
raise WechatOauth::Error.new(result['errcode'], result['errmsg'])
end
result
end
# https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
# response:
# {
# "access_token":"ACCESS_TOKEN",
# "expires_in":7200,
# "refresh_token":"REFRESH_TOKEN",
# "openid":"OPENID",
# "scope":"SCOPE",
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
# }
def access_token(code)
params = {
appid: WechatOauth.appid,
secret: WechatOauth.secret,
code: code,
grant_type: 'authorization_code'
}
request(:get, '/sns/oauth2/access_token', params)
end
# https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
# response:
# {
# "openid":"OPENID",
# "nickname":"NICKNAME",
# "sex":1,
# "province":"PROVINCE",
# "city":"CITY",
# "country":"COUNTRY",
# "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
# "privilege":[
# "PRIVILEGE1",
# "PRIVILEGE2"
# ],
# "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
#
# }
def user_info(access_token, openid)
request(:get, '/sns/userinfo', access_token: access_token, openid: openid)
end
end

@ -9,6 +9,8 @@ class Attachment < ApplicationRecord
belongs_to :course, foreign_key: :container_id, optional: true belongs_to :course, foreign_key: :container_id, optional: true
has_many :attachment_group_settings, :dependent => :destroy has_many :attachment_group_settings, :dependent => :destroy
has_many :attachment_histories, -> { order(version: :desc) }, :dependent => :destroy has_many :attachment_histories, -> { order(version: :desc) }, :dependent => :destroy
# 二级目录
belongs_to :course_second_category, optional: true
scope :by_filename_or_user_name, -> (keywords) { joins(:author).where("filename like :search or LOWER(concat(users.lastname, users.firstname)) LIKE :search", scope :by_filename_or_user_name, -> (keywords) { joins(:author).where("filename like :search or LOWER(concat(users.lastname, users.firstname)) LIKE :search",
:search => "%#{keywords.split(" ").join('|')}%") unless keywords.blank? } :search => "%#{keywords.split(" ").join('|')}%") unless keywords.blank? }
@ -96,7 +98,7 @@ class Attachment < ApplicationRecord
def become_history def become_history
history = self.attachment_histories.first history = self.attachment_histories.first
new_attachment_history = AttachmentHistory.new(self.attributes.except("id", "resource_bank_id", "unified_setting", "course_second_category_id").merge( new_attachment_history = AttachmentHistory.new(self.attributes.except("id", "resource_bank_id", "unified_setting", "course_second_category_id", "delay_publish").merge(
attachment_id: self.id, attachment_id: self.id,
version: history.nil? ? 1 : history.version + 1, version: history.nil? ? 1 : history.version + 1,
)) ))
@ -104,7 +106,7 @@ class Attachment < ApplicationRecord
end end
def copy_attributes_from_new_attachment(new_attachment) def copy_attributes_from_new_attachment(new_attachment)
self.attributes = new_attachment.attributes.dup.except("id","container_id","container_type","is_public","downloads", "quotes",'is_publish','publish_time') self.attributes = new_attachment.attributes.dup.except("id","container_id","container_type","is_public","downloads", "quotes",'is_publish','publish_time', "delay_publish")
end end
def set_public(is_public) def set_public(is_public)

@ -18,6 +18,29 @@ class Competition < ApplicationRecord
after_create :create_competition_modules after_create :create_competition_modules
def mode_type
case mode
when 1
"课堂"
when 2
"实训"
when 3
"教学"
when 4
"托管"
else
"--"
end
end
def teacher_staff_num
teacher_staff ? "#{teacher_staff.minimum}~#{teacher_staff.maximum}" : "--"
end
def member_staff_num
member_staff ? "#{member_staff.minimum}~#{member_staff.maximum}" : "--"
end
# 是否上架 # 是否上架
def published? def published?
status? status?

@ -0,0 +1,3 @@
class CompetitionModeSetting < ApplicationRecord
belongs_to :course
end

@ -37,6 +37,7 @@ class HomeworkCommon < ApplicationRecord
validates :name, length: { maximum: 60 } validates :name, length: { maximum: 60 }
validates :description, length: { maximum: 15000 } validates :description, length: { maximum: 15000 }
validates :explanation, length: { maximum: 5000 }
validates :reference_answer, length: { maximum: 15000 } validates :reference_answer, length: { maximum: 15000 }
# after_update :update_activity # after_update :update_activity

@ -6,6 +6,6 @@ class HomeworkGroupSetting < ApplicationRecord
scope :none_published, -> {where("homework_group_settings.publish_time IS NULL OR homework_group_settings.publish_time > ?", Time.now)} scope :none_published, -> {where("homework_group_settings.publish_time IS NULL OR homework_group_settings.publish_time > ?", Time.now)}
scope :published_no_end, -> {where("homework_group_settings.publish_time IS NOT NULL AND homework_group_settings.publish_time < ? scope :published_no_end, -> {where("homework_group_settings.publish_time IS NOT NULL AND homework_group_settings.publish_time < ?
and homework_group_settings.end_time > ?", Time.now, Time.now)} and homework_group_settings.end_time > ?", Time.now, Time.now)}
scope :none_end, -> {where("homework_group_settings.end_time IS NOT NULL AND homework_group_settings.end_time > ?", Time.now)} scope :none_end, -> {where("homework_group_settings.end_time IS NULL or homework_group_settings.end_time > ?", Time.now)}
end end

@ -6,8 +6,12 @@ class Laboratory < ApplicationRecord
has_one :laboratory_setting, dependent: :destroy has_one :laboratory_setting, dependent: :destroy
has_many :portal_images, dependent: :destroy
validates :identifier, uniqueness: { case_sensitive: false }, allow_nil: true validates :identifier, uniqueness: { case_sensitive: false }, allow_nil: true
delegate :name, :navbar, :footer, :login_logo_url, :nav_logo_url, :tab_logo_url, :default_navbar, to: :laboratory_setting
def site def site
rails_env = EduSetting.get('rails_env') rails_env = EduSetting.get('rails_env')
suffix = rails_env && rails_env != 'production' ? ".#{rails_env}.educoder.net" : '.educoder.net' suffix = rails_env && rails_env != 'production' ? ".#{rails_env}.educoder.net" : '.educoder.net'
@ -19,7 +23,7 @@ class Laboratory < ApplicationRecord
return if subdomain.blank? return if subdomain.blank?
rails_env = EduSetting.get('rails_env') rails_env = EduSetting.get('rails_env')
subdomain = subdomain.slice(0, subdomain.size - rails_env.size - 1) if subdomain.end_with?(rails_env) # winse.dev => winse subdomain = subdomain.slice(0, subdomain.size - rails_env.size - 1) if rails_env && subdomain.end_with?(rails_env) # winse.dev => winse
find_by_identifier(subdomain) find_by_identifier(subdomain)
end end

@ -0,0 +1,11 @@
class OpenUser < ApplicationRecord
belongs_to :user
validates :uid, presence: true, uniqueness: { scope: :type }
serialize :extra, JSON
def can_bind_cache_key
"open_user:#{type}:#{uid}:can_bind"
end
end

@ -0,0 +1,9 @@
class OpenUsers::QQ < OpenUser
def nickname
extra&.[]('nickname')
end
def en_type
'qq'
end
end

@ -0,0 +1,9 @@
class OpenUsers::Wechat < OpenUser
def nickname
extra&.[]('nickname')
end
def en_type
'qq'
end
end

@ -1,4 +1,8 @@
class PortalImage < ApplicationRecord class PortalImage < ApplicationRecord
belongs_to :laboratory
scope :only_online, -> { where(status: true) }
def online? def online?
status? status?
end end

@ -28,9 +28,12 @@ class User < ApplicationRecord
MIX_PASSWORD_LIMIT = 8 MIX_PASSWORD_LIMIT = 8
CHARS = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z) LOGIN_CHARS = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z).freeze
has_one :user_extension, dependent: :destroy has_one :user_extension, dependent: :destroy
has_many :open_users, dependent: :destroy
has_one :wechat_open_user, class_name: 'OpenUsers::Wechat'
has_one :qq_open_user, class_name: 'OpenUsers::QQ'
accepts_nested_attributes_for :user_extension, update_only: true accepts_nested_attributes_for :user_extension, update_only: true
has_many :memos, foreign_key: 'author_id' has_many :memos, foreign_key: 'author_id'
@ -40,7 +43,7 @@ class User < ApplicationRecord
has_many :myshixuns, :dependent => :destroy has_many :myshixuns, :dependent => :destroy
has_many :study_shixuns, through: :myshixuns, source: :shixun # 已学习的实训 has_many :study_shixuns, through: :myshixuns, source: :shixun # 已学习的实训
has_many :course_messages has_many :course_messages
has_many :courses, dependent: :destroy has_many :courses, foreign_key: 'tea_id', dependent: :destroy
#试卷 #试卷
has_many :exercise_banks, :dependent => :destroy has_many :exercise_banks, :dependent => :destroy
@ -166,6 +169,8 @@ class User < ApplicationRecord
# validates_format_of :mail, with: VALID_EMAIL_REGEX, multiline: true # validates_format_of :mail, with: VALID_EMAIL_REGEX, multiline: true
# validates_format_of :phone, with: VALID_PHONE_REGEX, multiline: true # validates_format_of :phone, with: VALID_PHONE_REGEX, multiline: true
validate :validate_password_length validate :validate_password_length
#validate :validate_ID_number
#validates_format_of :ID_number, with: VALID_NUMBER_REGEX, multiline: true, message: "身份证号格式不对"
# validates :nickname, presence: true, length: { maximum: 10 } # validates :nickname, presence: true, length: { maximum: 10 }
# validates :lastname, presence: true # validates :lastname, presence: true
@ -242,6 +247,11 @@ class User < ApplicationRecord
user_extension&.department&.name || '' user_extension&.department&.name || ''
end end
# 课堂的所有身份
def course_role course
course.course_members.where(user_id: id).pluck(:role)
end
# 课堂的老师(创建者、老师、助教) # 课堂的老师(创建者、老师、助教)
def teacher_of_course?(course) def teacher_of_course?(course)
course.course_members.exists?(user_id: id, role: [1,2,3], is_active: 1) || admin? || business? course.course_members.exists?(user_id: id, role: [1,2,3], is_active: 1) || admin? || business?
@ -264,7 +274,7 @@ class User < ApplicationRecord
# 课堂的学生 # 课堂的学生
def student_of_course?(course) def student_of_course?(course)
course.course_members.exists?(user_id: id, role: %i[STUDENT]) course.course_members.exists?(user_id: id, role: %i[STUDENT], is_active: 1)
end end
# 课堂成员 # 课堂成员
@ -583,6 +593,16 @@ class User < ApplicationRecord
mail.present? mail.present?
end end
# 手机号123***123
def hidden_phone
Util.conceal(phone, :phone).to_s
end
# 邮箱w***l@qq.com
def hidden_mail
Util.conceal(mail, :email).to_s
end
# 学院的url标识 # 学院的url标识
def college_identifier def college_identifier
Department.find_by_id(department_members.pluck(:department_id).first)&.identifier Department.find_by_id(department_members.pluck(:department_id).first)&.identifier
@ -613,13 +633,21 @@ class User < ApplicationRecord
admin? || business? admin? || business?
end end
def self.generate_login(prefix = 'p') def self.generate_login(prefix)
code = CHARS.sample(8).join login = prefix + LOGIN_CHARS.sample(8).join('')
while User.exists?(login: prefix + code) do while User.exists?(login: login)
code = CHARS.sample(8).join login = prefix + LOGIN_CHARS.sample(8).join('')
end
login
end end
prefix + code def bind_open_user?(type)
case type
when 'wechat' then wechat_open_user.present?
when 'qq' then qq_open_user.present?
else false
end
end end
protected protected

@ -15,7 +15,7 @@ class Admins::LaboratoryQuery < ApplicationQuery
keyword = strip_param(:keyword) keyword = strip_param(:keyword)
if keyword.present? if keyword.present?
like_sql = 'schools.name LIKE :keyword OR laboratories.identifier LIKE :keyword' like_sql = 'schools.name LIKE :keyword OR laboratories.identifier LIKE :keyword'
laboratories = laboratories.joins(:school).where(like_sql, keyword: "%#{keyword}%") laboratories = laboratories.left_joins(:school).where(like_sql, keyword: "%#{keyword}%")
end end
custom_sort laboratories, params[:sort_by], params[:sort_direction] custom_sort laboratories, params[:sort_by], params[:sort_direction]

@ -37,6 +37,9 @@ class Admins::UserQuery < ApplicationQuery
users = users.where('CONCAT(lastname, firstname) LIKE :name', name: "%#{name}%") users = users.where('CONCAT(lastname, firstname) LIKE :name', name: "%#{name}%")
end end
# 单位ID
users = users.joins(:user_extension).where(user_extensions: { school_id: params[:school_id] }) if params[:school_id].present?
# 学校名称 # 学校名称
school_name = params[:school_name].to_s.strip.presence school_name = params[:school_name].to_s.strip.presence
users = users.joins(user_extension: :school).where('schools.name LIKE ?', "%#{school_name}%") if school_name users = users.joins(user_extension: :school).where('schools.name LIKE ?', "%#{school_name}%") if school_name

@ -1,9 +1,10 @@
class Admins::DragPortalImageService < ApplicationService class Admins::DragPortalImageService < ApplicationService
Error = Class.new(StandardError) Error = Class.new(StandardError)
attr_reader :move, :after attr_reader :laboratory, :move, :after
def initialize(move, after) def initialize(laboratory, move, after)
@laboratory = laboratory
@move = move @move = move
@after = after # 移动后下一个位置的元素 @after = after # 移动后下一个位置的元素
end end
@ -11,7 +12,7 @@ class Admins::DragPortalImageService < ApplicationService
def call def call
return if move.position + 1 == after&.position # 未移动 return if move.position + 1 == after&.position # 未移动
images = PortalImage.all images = laboratory.portal_images
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
if after.blank? || move.id == after.id # 移动至末尾 if after.blank? || move.id == after.id # 移动至末尾
@ -31,5 +32,4 @@ class Admins::DragPortalImageService < ApplicationService
end end
end end
end end
end end

@ -30,7 +30,7 @@ class Admins::SaveLaboratorySettingService < ApplicationService
hash = {} hash = {}
hash[:name] = strip nav[:name] hash[:name] = strip nav[:name]
hash[:link] = strip nav[:link] hash[:link] = strip nav[:link]
hash[:hidden] = nav[:hidden].to_s == 0 hash[:hidden] = nav[:hidden].to_s != '0'
hash hash
end end
end end

@ -1,6 +1,8 @@
class ApplicationService class ApplicationService
include Callable include Callable
Error = Class.new(StandardError)
private private
def strip(str) def strip(str)

@ -0,0 +1,53 @@
class CreateBindUserService < ApplicationService
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
raise Error, '系统错误' if open_user.blank?
raise Error, '系统错误' unless can_bind_user?
if params[:not_bind].to_s == 'true'
clear_can_bind_user_flag
return user
end
bind_user = User.try_to_login(params[:username], params[:password])
raise Error, '用户名或者密码错误' if bind_user.blank?
raise Error, '该账号已被其他微信号绑定,请更换其他账号进行绑定' if bind_user.bind_open_user?(params[:type].to_s)
ActiveRecord::Base.transaction do
open_user.user_id = bind_user.id
open_user.save!
user.user_extension.delete
user.delete
end
clear_can_bind_user_flag
bind_user
end
private
def open_user
@_open_user ||= begin
case params[:type].to_s
when 'wechat' then user.wechat_open_user
when 'qq' then user.qq_open_user
end
end
end
def can_bind_user?
Rails.cache.read(open_user.can_bind_cache_key).present?
end
def clear_can_bind_user_flag
Rails.cache.delete(open_user.can_bind_cache_key)
end
end

@ -0,0 +1,38 @@
class Oauth::CreateOrFindQqAccountService < ApplicationService
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
new_user = false
# 存在该用户
open_user = OpenUsers::QQ.find_by(uid: params['uid'])
return [open_user.user, new_user] if open_user.present?
if user.blank? || !user.logged?
new_user = true
# 新用户
login = User.generate_login('q')
@user = User.new(login: login, nickname: params.dig('info', 'nickname'), type: 'User', status: User::STATUS_ACTIVE)
end
ActiveRecord::Base.transaction do
if user.new_record?
user.save!
gender = params.dig('extra', 'raw_info', 'gender') == '女' ? 1 : 0
user.create_user_extension!(gender: gender)
end
new_open_user = OpenUsers::QQ.create!(user: user, uid: params['uid'], extra: params.dig('extra', 'raw_info'))
Rails.cache.write(new_open_user.can_bind_cache_key, 1, expires_in: 1.hours) if new_user # 方便后面进行账号绑定
end
[user, new_user]
end
end

@ -0,0 +1,57 @@
class Oauth::CreateOrFindWechatAccountService < ApplicationService
Error = Class.new(StandardError)
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
code = params['code'].to_s.strip
raise Error, 'Code不能为空' if code.blank?
new_user = false
result = WechatOauth::Service.access_token(code)
result = WechatOauth::Service.user_info(result['access_token'], result['openid'])
# 存在该用户
open_user = OpenUsers::Wechat.find_by(uid: result['unionid'])
return [open_user.user, new_user] if open_user.present?
if user.blank? || !user.logged?
new_user = true
# 新用户
login = User.generate_login('w')
@user = User.new(login: login, nickname: result['nickname'], type: 'User', status: User::STATUS_ACTIVE)
end
ActiveRecord::Base.transaction do
if new_user
user.save!
gender = result['sex'].to_i == 1 ? 0 : 1
user.create_user_extension!(gender: gender)
# 下载头像
avatar_path = Util::FileManage.source_disk_filename(user)
Util.download_file(result['headimgurl'], avatar_path)
end
new_open_user= OpenUsers::Wechat.create!(user: user, uid: result['unionid'], extra: result)
Rails.cache.write(new_open_user.can_bind_cache_key, 1, expires_in: 1.hours) if new_user # 方便后面进行账号绑定
end
[user, new_user]
rescue WechatOauth::Error => ex
raise Error, ex.message
end
private
def code
params[:code].to_s.strip
end
end

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

Loading…
Cancel
Save