本地版调整

dev_local_2
杨树林 5 years ago
commit 93648dffeb

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

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

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

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

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

@ -64,4 +64,12 @@ function customConfirm(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 {
& > .action {
padding: 0 3px;
}
.more-action-dropdown {
.dropdown-item {
font-size: 14px;
}
}
}
/* 分页 */

@ -31,6 +31,7 @@
display: block;
width: 80px;
height: 80px;
background: #f0f0f0;
}
&-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
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: 验证手机号是否有效 # 如果有新的继续后面加
# login_type 1手机类型 2邮箱类型

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

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

@ -0,0 +1,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|
unless tag_repertoire_ids.include?(id)
tag_repertoire = @shixun.shixun_tag_repertoires.new(shixun_id:@shixun.id,tag_repertoire_id:id)
tag_repertoire.save
tag_repertoire.save!
end
end
else

@ -8,6 +8,7 @@ class ApplicationController < ActionController::Base
include GitHelper
include LoggerHelper
include LaboratoryHelper
include LoginHelper
protect_from_forgery prepend: true, unless: -> { request.format.json? }
@ -235,12 +236,6 @@ class ApplicationController < ActionController::Base
# 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
# reacct静态资源加载不需要走这一步
return if params[:controller] == "main"
@ -281,17 +276,6 @@ class ApplicationController < ActionController::Base
# User.current = User.find 81403
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
# and starts a session if needed
def find_current_user
@ -307,10 +291,6 @@ class ApplicationController < ActionController::Base
end
end
def autologin_cookie_name
edu_setting('autologin_cookie_name').presence || 'autologin'
end
def try_to_autologin
if cookies[autologin_cookie_name]
# auto-login feature starts a new session
@ -358,9 +338,9 @@ class ApplicationController < ActionController::Base
# 如果代码窗口是隐藏的,则不用保存代码
return if myshixun.shixun.hide_code || myshixun.shixun.vnc
file_content = git_fle_content myshixun.repo_path, path
unless file_content.present?
raise("获取文件代码异常")
end
#unless file_content.present?
# raise("获取文件代码异常")
#end
logger.info("#######game_id:#{game_id}, file_content:#{file_content}")
game_code = GameCode.where(:game_id => game_id, :path => path).first
if game_code.nil?
@ -621,22 +601,6 @@ class ApplicationController < ActionController::Base
cookies[:fileDownload] = true
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课程的评审用户数据创建包含创建课堂学生
def open_class_user
user = User.find_by(login: "OpenClassUser")

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

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

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

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

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

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

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

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

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

@ -35,7 +35,7 @@ class CoursesController < ApplicationController
:transfer_to_course_group, :delete_from_course, :export_member_scores_excel,
:search_users, :add_students_by_search, :get_historical_courses, :add_teacher_popup,
: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,
:set_course_group, :create_group_by_importing_file,
:update_task_position, :tasks_list]
@ -583,6 +583,8 @@ class CoursesController < ApplicationController
# 学生身份的处理
student_member = course_members.where(role: %i[STUDENT]).take
# 不存在则创建学生身份
if params[:roles].include?("STUDENT") && student_member.blank?
correspond_teacher_exist = CourseMember.exists?(user_id: params[:user_id], is_active: 1, course_id: @course.id, role: %i[CREATOR PROFESSOR ASSISTANT_PROFESSOR])
new_student = CourseMember.new(user_id: params[:user_id], course_id: @course.id, role: 4)
@ -597,6 +599,9 @@ class CoursesController < ApplicationController
student_member.destroy!
CourseDeleteStudentDeleteWorksJob.perform_later(@course.id, [params[:user_id]])
# CourseDeleteStudentNotifyJob.perform_later(@course.id, [params[:user_id]], current_user.id)
elsif params[:roles].include?("STUDENT") && student_member.present? && !params[:roles].include?("PROFESSOR") && !params[:roles].include?("ASSISTANT_PROFESSOR")
# 学生身份存在且学生没有教师身份时更新is_active
student_member.update_attributes!(is_active: 1)
end
normal_status(0, "修改成功")
@ -681,13 +686,19 @@ class CoursesController < ApplicationController
course_member = @course.course_members.find_by!(user_id: current_user.id, is_active: 1)
tip_exception("切换失败") if course_member.STUDENT?
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_student.update_attributes(is_active: 1)
course_student = CourseMember.find_by(user_id: current_user.id, role: %i[STUDENT], course_id: @course.id)
course_member.update_attributes!(is_active: 0)
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, "切换成功")
rescue => e
uid_logger_error("switch_to_student error: #{e.message}")
tip_exception("切换失败")
tip_exception(e.message)
raise ActiveRecord::Rollback
end
end
@ -1127,7 +1138,7 @@ class CoursesController < ApplicationController
def top_banner
@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
@course.increment!(:visits)
end

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

@ -518,27 +518,27 @@ class ExercisesController < ApplicationController
common_group = exercise_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的
new_group_ids = course_id - common_group #新传入的班级id
if common_group.count > 0 #判断试卷的分班设置是否存在,存在则更新,负责则新建
ex_group_params = {
exercise_group_sets = exercise_groups.find_in_exercise_group("course_group_id",common_group)
exercise_group_sets.each do |the_group_setting|
ex_group_params = {
:publish_time => exercise_publish_time,
:end_time => exercise_end_time
}
exercise_group_sets = exercise_groups.find_in_exercise_group("course_group_id",common_group)
the_group_setting = exercise_group_sets.first
if the_group_setting.present?
}
the_group_setting_status = set_exercise_status(the_group_setting.publish_time,the_group_setting.end_time)
if the_group_setting_status == 2
ex_group_params = {
:publish_time => the_group_setting.publish_time,
:end_time => exercise_end_time
:publish_time => the_group_setting.publish_time,
:end_time => exercise_end_time
}
elsif the_group_setting_status == 3
ex_group_params = {
:publish_time => the_group_setting.publish_time,
:end_time => the_group_setting.end_time
:publish_time => the_group_setting.publish_time,
:end_time => the_group_setting.end_time
}
end
the_group_setting.update_attributes!(ex_group_params)
end
exercise_group_sets.update_all(ex_group_params)
end
if new_group_ids.size > 0
new_group_ids.each do |c|
@ -560,8 +560,9 @@ class ExercisesController < ApplicationController
error_count == 0
normal_status(-1,"已发布/已截止的试卷不允许修改时间")
else
# 未发布的分班设置才能删除
if old_exercise_groups.size > 0
old_all_ex_groups = exercise_groups.find_in_exercise_group("course_group_id",old_exercise_groups)
old_all_ex_groups = exercise_groups.find_in_exercise_group("course_group_id",old_exercise_groups).exercise_group_not_published
old_all_ex_groups.destroy_all
end
#试卷更新为exercise_group_setting的发布时间最小截止时间最大
@ -720,8 +721,8 @@ class ExercisesController < ApplicationController
if exercise.unified_setting
ex_status = exercise.exercise_status #则为试卷的状态
else
ex_status = exercise.exercise_group_settings.find_in_exercise_group("course_group_id",params[:group_ids])
.exercise_group_not_published.present? ? 1 : 0
ex_status = @course.course_groups.where(id: params[:group_ids]).size !=
exercise.exercise_group_settings.where(course_group_id: params[:group_ids]).exercise_group_published.size ? 1 : 0
end
if ex_status == 1 #如果试卷存在已发布的,或者是已截止的,那么则直接跳过
g_course = group_ids #表示是否传入分班参数,如果传入分班的参数,那么试卷的统一设置需修改
@ -739,8 +740,8 @@ class ExercisesController < ApplicationController
g_course.each_with_index do |i, index|
exercise_group_setting = exercise.exercise_group_settings.find_in_exercise_group("course_group_id",i).first #根据课堂分班的id寻找试卷所在的班级
group_end_time = params[:detail] ? group_end_times[index] : ex_end_time
if exercise_group_setting #如果该试卷分组存在,则更新,否则新建
exercise_group_setting.update_attributes(publish_time: Time.now, end_time: group_end_time)
if exercise_group_setting.present? #如果该试卷分组存在,则更新,否则新建
exercise_group_setting.update_attributes!(publish_time: Time.now, end_time: group_end_time)
else
p_course_group = {
:exercise_id => exercise.id,
@ -1152,7 +1153,7 @@ class ExercisesController < ApplicationController
# 1 老师权限0 学生权限
@is_teacher_or = (@user_course_identity < Course::STUDENT) ? 1 : 0
@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 = []
get_exercise_status = @exercise.get_exercise_status(current_user) #当前用户的试卷状态
@ex_answer_status = @exercise.get_exercise_status(@ex_user&.user) #当前试卷用户的试卷状态
@ -1323,7 +1324,7 @@ class ExercisesController < ApplicationController
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception("页面调用失败!")
tip_exception(e.message)
raise ActiveRecord::Rollback
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)
@paging_type = "percent"
# 按题型排序
percent_sort = "desc"
if params[:sort].present?
@paging_type = params[:sort].to_s
percent_sort = params[:sort]
end
# @paging_type = "percent"
# # 按题型排序
# if params[:sort].present?
# @paging_type = params[:sort].to_s
# end
ques_result_all = exercise_commit_result(@exercise_questions,@exercise_commit_user_ids)
if @paging_type == "percent"
@question_result_hash = ques_result_all.sort_by{|s| s[:percent]}
#默认降序排列
if percent_sort == "desc"
@question_result_hash = ques_result_all.sort_by{|s| s[:percent]}.reverse
else
@question_result_hash = ques_result_all.sort_by{|s| s[:"#{@paging_type}"]}
@question_result_hash = ques_result_all.sort_by{|s| s[:percent]}
end
@exercise_questions_count = @exercise_questions.size
@ -1709,9 +1716,9 @@ class ExercisesController < ApplicationController
ques_number = q.question_number
end
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
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
ques_status = 0
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 :validate_upload_params, only: %i[upload import]
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]
def index
sort = params[:sort] || 0 # 0: 降序1: 升序
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
@attachments = course_second_category_id.to_i == 0 ? @course.attachments : @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 = @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(author: [:user_extension, :course_members])
.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
@publish_count = @attachments.published.size
@unpublish_count = @total_count - @publish_count
@ -137,9 +138,9 @@ class FilesController < ApplicationController
def upload
attachment_ids = params[:attachment_ids]
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
publish_time = params[:publish_time]
course_group_publish_times = params[:course_group_publish_times] || []
# is_unified_setting = params.has_key?(:is_unified_setting) ? params[:is_unified_setting] : true
# publish_time = params[:publish_time]
# course_group_publish_times = params[:course_group_publish_times] || []
begin
attachment_ids.each do |attchment_id|
@ -148,9 +149,12 @@ class FilesController < ApplicationController
attachment.container = @course
attachment.course_second_category_id = course_second_category_id
attachment.description = params[:description]
attachment.is_public = params[:is_public] ? 1 : 0
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.is_public = params[:is_public] && @course.is_public == 1 ? 1 : 0
attachment.is_publish = @atta_is_publish
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!
end
end
@ -188,8 +192,9 @@ class FilesController < ApplicationController
attach_copied_obj.created_on = Time.now
attach_copied_obj.author = current_user
attach_copied_obj.is_public = 0
attach_copied_obj.is_publish = 1
attach_copied_obj.publish_time = Time.now
attach_copied_obj.is_publish = @atta_is_publish
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.copy_from = ori.copy_from.nil? ? ori.id : ori.copy_from
if attach_copied_obj.attachtype == nil
@ -209,11 +214,7 @@ class FilesController < ApplicationController
def update
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]
course_group_publish_times = params[:course_group_publish_times] || []
@old_attachment = @file
@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_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.save!
@new_attachment.delete
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
@old_attachment.description = params[:description]
end
@old_attachment.set_public(is_public)
# @old_attachment.set_public(is_public)
if is_unified_setting
@old_attachment.set_publish_time(publish_time)
@old_attachment.attachment_group_settings.destroy_all
end
# if is_unified_setting
# @old_attachment.set_publish_time(publish_time)
# @old_attachment.attachment_group_settings.destroy_all
# end
if publish_time.blank? && @course.course_groups.size > 0 && !is_unified_setting
@old_attachment.set_course_group_publish_time(@course, course_group_publish_times)
end
# if publish_time.blank? && @course.course_groups.size > 0 && !is_unified_setting
# @old_attachment.set_course_group_publish_time(@course, course_group_publish_times)
# end
@old_attachment.save!
rescue Exception => e
@ -304,11 +309,19 @@ class FilesController < ApplicationController
end
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
def validate_upload_params
find_attachment_ids
find_course_second_category_id
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

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

@ -160,7 +160,8 @@ class HomeworkCommonsController < ApplicationController
# 作品状态 0 未提交, 1 按时提交, 2 延迟提交
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)
@student_works = all_student_works.where(work_status: work_status)
@ -170,7 +171,8 @@ class HomeworkCommonsController < ApplicationController
# 分班情况
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)
end
@ -482,7 +484,7 @@ class HomeworkCommonsController < ApplicationController
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]).
update_all(publish_time: publish_time, end_time: end_time)
setting_group_ids << setting[:group_id]
@ -1170,7 +1172,7 @@ class HomeworkCommonsController < ApplicationController
# 可立即截止的分班:统一设置则是用户管理的所有分班,否则是当前用户管理的分班中已发布且未截止的
charge_group_ids = @course.charge_group_ids(@current_user) # 当前用户管理的分班
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)
else
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
pl_status = poll.polls_status #则为试卷的状态
else
pl_status = poll.poll_group_settings.find_in_poll_group("course_group_id",params[:group_ids]).poll_group_not_published.present? ? 1 : 0 #立即发布针对分组设置的全部未发布的班级才生效
pl_status = @course.course_groups.where(id: group_ids).size !=
poll.poll_group_settings.where(course_group_id: group_ids).poll_group_published.size ? 1 : 0 #立即发布针对分组设置的全部未发布的班级才生效
end
if pl_status == 1 #如果问卷存在已发布的,或者是已截止的,那么则直接跳过
g_course = group_ids #表示是否传入分班参数,如果传入分班的参数那么poll的统一设置需修改
@ -295,8 +296,8 @@ class PollsController < ApplicationController
g_course.each_with_index do |i, index|
poll_group_setting = poll.poll_group_settings.find_in_poll_group("course_group_id",i).first #根据课堂分班的id寻找问卷所在的班级
group_end_time = params[:detail] ? group_end_times[index] : ex_end_time
if poll_group_setting #如果该问卷分组存在,则更新,否则新建
poll_group_setting.update_attributes(publish_time: Time.now, end_time: group_end_time)
if poll_group_setting.present? #如果该问卷分组存在,则更新,否则新建
poll_group_setting.update_attributes!(publish_time: Time.now, end_time: group_end_time)
else
p_course_group = {
:poll_id => poll.id,
@ -306,7 +307,7 @@ class PollsController < ApplicationController
:end_time => group_end_time,
}
new_poll_group = poll.poll_group_settings.new p_course_group
new_poll_group.save
new_poll_group.save!
end
end
e_time = poll.poll_group_settings.end_time_present.map(&:end_time).max
@ -782,27 +783,27 @@ class PollsController < ApplicationController
common_group = poll_groups_ids & course_id #传入的班级与问卷已存在的班级的交集,即表示已有分班的
new_group_ids = course_id - common_group #新传入的班级id
if common_group.size > 0 #传入的参数存在已发布的分班
poll_group_params = {
poll_group = poll_groups.find_in_poll_group("course_group_id",common_group)
poll_group.each do |the_group_setting|
poll_group_params = {
:publish_time => poll_publish_time,
:end_time => poll_end_time
}
poll_group = poll_groups.find_in_poll_group("course_group_id",common_group)
the_group_setting = poll_group.first
if the_group_setting.present?
}
the_group_setting_status = set_poll_status(the_group_setting.publish_time,the_group_setting.end_time)
if the_group_setting_status == 2
poll_group_params = {
:publish_time => the_group_setting.publish_time,
:end_time => poll_end_time
:publish_time => the_group_setting.publish_time,
:end_time => poll_end_time
}
elsif the_group_setting_status == 3
poll_group_params = {
:publish_time => the_group_setting.publish_time,
:end_time => the_group_setting.end_time
:publish_time => the_group_setting.publish_time,
:end_time => the_group_setting.end_time
}
end
the_group_setting.update_attributes(poll_group_params)
end
poll_group.update_all(poll_group_params)
end
if new_group_ids.size > 0
new_group_ids.each do |c|
@ -824,8 +825,9 @@ class PollsController < ApplicationController
error_count == 0
normal_status(-1,"已发布/已截止的问卷不允许修改时间")
else
# 未发布的分班设置才能删除
if old_poll_groups.size > 0
old_all_poll_groups = poll_groups.find_in_poll_group("course_group_id",old_poll_groups)
old_all_poll_groups = poll_groups.find_in_poll_group("course_group_id",old_poll_groups).poll_group_not_published
old_all_poll_groups.destroy_all
end
#问卷更新为poll_group_setting的发布时间最小截止时间最大

@ -91,12 +91,16 @@ class QuestionBanksController < ApplicationController
banks = @object_type.classify.constantize.where(id: params[:object_id])
course = current_user.manage_courses.find_by!(id: params[:course_id])
task_ids = []
homework_type = ""
container_type = ""
banks.each do |bank|
case @object_type
when 'HomeworkBank' # 作业
task = quote_homework_bank bank, course
homework_type = task.homework_type
when 'ExerciseBank'
if bank.container_type == 'Exercise' # 试卷
container_type = bank.container_type
if container_type == 'Exercise' # 试卷
task = quote_exercise_bank bank, course
else # 问卷
task = quote_poll_bank bank, course
@ -108,7 +112,23 @@ class QuestionBanksController < ApplicationController
end
task_ids << task.id if task
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
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
validates :password, presence: true
validates :old_password, presence: true
end

@ -1,110 +1,3 @@
module Admins::BaseHelper
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
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
include ManageBackHelper
end

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

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

@ -10,9 +10,9 @@ module ExercisesHelper
exercise_obj_status.each do |q|
q_type = q.question_type
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
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
if q_type <= Exercise::JUDGMENT
@ -39,8 +39,13 @@ module ExercisesHelper
else
ques_score = 0.0
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
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
if ques_score >= q.question_score #满分作答为正确
@ -64,7 +69,7 @@ module ExercisesHelper
exercise_sub_status = exercise_questions.find_by_custom("question_type",Exercise::SUBJECTIVE) #主观题
@ex_sub_array = [] #主观题的已答/未答
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 s.question_score <= sub_answer.first.score
stand_status = 1
@ -115,14 +120,27 @@ module ExercisesHelper
if ex.question_type > Exercise::COMPLETION #当为主观题和实训题时,
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) #正确率
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 = []
if ex.question_type <= Exercise::JUDGMENT #选择题和判断题
ex_choices = ex.exercise_choices
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 #多选题
right_user_ids = user_ids
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)
end
right_users_count = right_user_ids.size
# right_users_scores = right_users_count * ex&.question_score.to_f
else #单选题和判断题
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_scores = right_users_count * ex&.question_score.to_f
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
@ -159,6 +180,7 @@ module ExercisesHelper
null_stand_choice = null_standard_answer.pluck(:exercise_choice_id) #一个exercise_choice_id可能对应多个answer_text
null_stand_text = null_standard_answer.pluck(:answer_text)
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
null_stand_choice.each_with_index do |s,index|
user_count = 0
@ -181,7 +203,10 @@ module ExercisesHelper
all_user_count += user_count
standard_answer_count += 1
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 )
@ -772,16 +797,24 @@ module ExercisesHelper
question_comment = []
# user_score_pre = nil
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
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
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的分数时为空
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
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 #选择题,判断题根据第一个记录查分
user_score = user_score_pre.present? ? user_score_pre.first.score : 0.0
@ -829,7 +862,7 @@ module ExercisesHelper
if ex_type == 4 #填空题/主观题/实训题有评论的
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
{
"user_score": (user_score.present? ? user_score.round(1).to_s : nil),

@ -286,10 +286,18 @@ module ExportHelper
@user_columns = []
ques_type_boolean = question_types.include?(4)
if ques_type_boolean #仅存在主观题或客观题的时候
@table_columns = @table_columns + %w(客观题成绩 主观题成绩 最终成绩 开始答题时间 提交时间)
@table_columns = @table_columns + %w(客观题成绩 主观题成绩 最终成绩)
else
@table_columns = @table_columns + %w(最终成绩 开始答题时间 提交时间)
@table_columns = @table_columns + %w(最终成绩)
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|
user_info = e_user.user
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_student_id,user_course,user_commit_stu]
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,user_start_time,user_end_time]
other_user_option = [user_score]
end
user_option = user_option + other_user_option
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
answers_content = q.exercise_answers.select{|answer| answer.user_id == e_user.user_id}
end
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)
end
end
@ -419,7 +452,7 @@ module ExportHelper
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 = 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_function
@ -29,6 +31,16 @@ module Util
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)
Rails.logger.error(exception.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
has_many :attachment_group_settings, :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",
:search => "%#{keywords.split(" ").join('|')}%") unless keywords.blank? }
@ -96,7 +98,7 @@ class Attachment < ApplicationRecord
def become_history
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,
version: history.nil? ? 1 : history.version + 1,
))
@ -104,7 +106,7 @@ class Attachment < ApplicationRecord
end
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
def set_public(is_public)

@ -18,6 +18,29 @@ class Competition < ApplicationRecord
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?
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 :description, length: { maximum: 15000 }
validates :explanation, length: { maximum: 5000 }
validates :reference_answer, length: { maximum: 15000 }
# 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 :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)}
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

@ -6,8 +6,12 @@ class Laboratory < ApplicationRecord
has_one :laboratory_setting, dependent: :destroy
has_many :portal_images, dependent: :destroy
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
rails_env = EduSetting.get('rails_env')
suffix = rails_env && rails_env != 'production' ? ".#{rails_env}.educoder.net" : '.educoder.net'
@ -19,7 +23,7 @@ class Laboratory < ApplicationRecord
return if subdomain.blank?
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)
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
belongs_to :laboratory
scope :only_online, -> { where(status: true) }
def online?
status?
end

@ -28,9 +28,12 @@ class User < ApplicationRecord
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_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
has_many :memos, foreign_key: 'author_id'
@ -40,7 +43,7 @@ class User < ApplicationRecord
has_many :myshixuns, :dependent => :destroy
has_many :study_shixuns, through: :myshixuns, source: :shixun # 已学习的实训
has_many :course_messages
has_many :courses, dependent: :destroy
has_many :courses, foreign_key: 'tea_id', 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 :phone, with: VALID_PHONE_REGEX, multiline: true
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 :lastname, presence: true
@ -242,6 +247,11 @@ class User < ApplicationRecord
user_extension&.department&.name || ''
end
# 课堂的所有身份
def course_role course
course.course_members.where(user_id: id).pluck(:role)
end
# 课堂的老师(创建者、老师、助教)
def teacher_of_course?(course)
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)
course.course_members.exists?(user_id: id, role: %i[STUDENT])
course.course_members.exists?(user_id: id, role: %i[STUDENT], is_active: 1)
end
# 课堂成员
@ -583,6 +593,16 @@ class User < ApplicationRecord
mail.present?
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标识
def college_identifier
Department.find_by_id(department_members.pluck(:department_id).first)&.identifier
@ -613,13 +633,21 @@ class User < ApplicationRecord
admin? || business?
end
def self.generate_login(prefix = 'p')
code = CHARS.sample(8).join
while User.exists?(login: prefix + code) do
code = CHARS.sample(8).join
def self.generate_login(prefix)
login = prefix + LOGIN_CHARS.sample(8).join('')
while User.exists?(login: login)
login = prefix + LOGIN_CHARS.sample(8).join('')
end
prefix + code
login
end
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
protected

@ -15,7 +15,7 @@ class Admins::LaboratoryQuery < ApplicationQuery
keyword = strip_param(:keyword)
if keyword.present?
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
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}%")
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
users = users.joins(user_extension: :school).where('schools.name LIKE ?', "%#{school_name}%") if school_name

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

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

@ -1,6 +1,8 @@
class ApplicationService
include Callable
Error = Class.new(StandardError)
private
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