commit
fe860fd2a1
@ -0,0 +1,125 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
if ($('body.admins-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: '/admins/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: '/admins/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: '/admins/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.admin-add-carousel-modal');
|
||||
var $createForm = $createModal.find('form.admin-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.admin-upload-file-modal').on('upload:success', function(e, data){
|
||||
var $carouselItem = $('.custom-carousel-item-' + data.source_id);
|
||||
$carouselItem.find('.custom-carousel-item-img img').attr('src', data.url);
|
||||
})
|
||||
}
|
||||
})
|
@ -0,0 +1,86 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
if ($('body.admins-laboratory-settings-show-page, body.admins-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,164 @@
|
||||
$(document).on('turbolinks:load', function() {
|
||||
if ($('body.admins-laboratories-index-page').length > 0) {
|
||||
var $searchContainer = $('.laboratory-list-form');
|
||||
var $searchForm = $searchContainer.find('form.search-form');
|
||||
var $list = $('.laboratory-list-container');
|
||||
|
||||
// ============== 新建 ===============
|
||||
var $modal = $('.modal.admin-create-laboratory-modal');
|
||||
var $form = $modal.find('form.admin-create-laboratory-form');
|
||||
var $schoolSelect = $modal.find('.school-select');
|
||||
|
||||
$form.validate({
|
||||
errorElement: 'span',
|
||||
errorClass: 'danger text-danger',
|
||||
rules: {
|
||||
school_id: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
school_id: {
|
||||
required: '请选择所属单位'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// modal ready fire
|
||||
$modal.on('show.bs.modal', function () {
|
||||
$schoolSelect.select2('val', ' ');
|
||||
});
|
||||
|
||||
// ************** 学校选择 *************
|
||||
var matcherFunc = function(params, data){
|
||||
if ($.trim(params.term) === '') {
|
||||
return data;
|
||||
}
|
||||
if (typeof data.text === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.name && data.name.indexOf(params.term) > -1) {
|
||||
var modifiedData = $.extend({}, data, true);
|
||||
return modifiedData;
|
||||
}
|
||||
|
||||
// Return `null` if the term should not be displayed
|
||||
return null;
|
||||
};
|
||||
|
||||
var defineSchoolSelect = function(schools) {
|
||||
$schoolSelect.select2({
|
||||
theme: 'bootstrap4',
|
||||
placeholder: '请选择单位',
|
||||
minimumInputLength: 1,
|
||||
data: schools,
|
||||
templateResult: function (item) {
|
||||
if(!item.id || item.id === '') return item.text;
|
||||
return item.name;
|
||||
},
|
||||
templateSelection: function(item){
|
||||
if (item.id) {
|
||||
$('#school_id').val(item.id);
|
||||
}
|
||||
return item.name || item.text;
|
||||
},
|
||||
matcher: matcherFunc
|
||||
});
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/api/schools/for_option.json',
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
defineSchoolSelect(data.schools);
|
||||
}
|
||||
});
|
||||
|
||||
$modal.on('click', '.submit-btn', function(){
|
||||
$form.find('.error').html('');
|
||||
|
||||
if ($form.valid()) {
|
||||
var url = $form.data('url');
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
dataType: 'json',
|
||||
url: url,
|
||||
data: $form.serialize(),
|
||||
success: function(){
|
||||
$.notify({ message: '创建成功' });
|
||||
$modal.modal('hide');
|
||||
|
||||
setTimeout(function(){
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
},
|
||||
error: function(res){
|
||||
var data = res.responseJSON;
|
||||
$form.find('.error').html(data.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============= 添加管理员 ==============
|
||||
var $addMemberModal = $('.admin-add-laboratory-user-modal');
|
||||
var $addMemberForm = $addMemberModal.find('.admin-add-laboratory-user-form');
|
||||
var $memberSelect = $addMemberModal.find('.laboratory-user-select');
|
||||
var $laboratoryIdInput = $addMemberForm.find('input[name="laboratory_id"]')
|
||||
|
||||
$addMemberModal.on('show.bs.modal', function(event){
|
||||
var $link = $(event.relatedTarget);
|
||||
var laboratoryId = $link.data('laboratory-id');
|
||||
$laboratoryIdInput.val(laboratoryId);
|
||||
|
||||
$memberSelect.select2('val', ' ');
|
||||
});
|
||||
|
||||
$memberSelect.select2({
|
||||
theme: 'bootstrap4',
|
||||
placeholder: '请输入要添加的管理员姓名',
|
||||
multiple: true,
|
||||
minimumInputLength: 1,
|
||||
ajax: {
|
||||
delay: 500,
|
||||
url: '/admins/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;
|
||||
},
|
||||
templateSelection: function(item){
|
||||
if (item.id) {
|
||||
}
|
||||
return item.real_name || item.text;
|
||||
}
|
||||
});
|
||||
|
||||
$addMemberModal.on('click', '.submit-btn', function(){
|
||||
$addMemberForm.find('.error').html('');
|
||||
|
||||
var laboratoryId = $laboratoryIdInput.val();
|
||||
var memberIds = $memberSelect.val();
|
||||
if (laboratoryId && memberIds && memberIds.length > 0) {
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
dataType: 'script',
|
||||
url: '/admins/laboratories/' + laboratoryId + '/laboratory_user',
|
||||
data: { user_ids: memberIds }
|
||||
});
|
||||
} else {
|
||||
$addMemberModal.modal('hide');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
@ -0,0 +1,2 @@
|
||||
// Place all the behaviors and hooks related to the matching controller here.
|
||||
// All this logic will automatically be available in application.js.
|
@ -0,0 +1,60 @@
|
||||
.admins-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,99 @@
|
||||
.admins-laboratories-index-page {
|
||||
.laboratory-list-table {
|
||||
.member-container {
|
||||
.laboratory-user {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.laboratory-user-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
padding: 2px 5px;
|
||||
margin: 2px 2px;
|
||||
border: 1px solid #91D5FF;
|
||||
background-color: #E6F7FF;
|
||||
color: #91D5FF;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.admins-laboratory-settings-show-page, .admins-laboratory-settings-update-page {
|
||||
.edit-laboratory-setting-container {
|
||||
.logo-item {
|
||||
display: flex;
|
||||
|
||||
&-img {
|
||||
display: block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
&-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,3 @@
|
||||
// Place all the styles related to the subject_lists controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
@ -0,0 +1,80 @@
|
||||
class Admins::CarouselsController < Admins::BaseController
|
||||
before_action :convert_file!, only: [:create]
|
||||
|
||||
def index
|
||||
@images = PortalImage.order(position: :asc)
|
||||
end
|
||||
|
||||
def create
|
||||
position = PortalImage.count + 1
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
image = PortalImage.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 admins_carousels_path
|
||||
end
|
||||
|
||||
def update
|
||||
current_image.update!(update_params)
|
||||
render_ok
|
||||
end
|
||||
|
||||
def destroy
|
||||
ActiveRecord::Base.transaction do
|
||||
current_image.destroy!
|
||||
# 前移
|
||||
PortalImage.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 = PortalImage.find_by(id: params[:move_id])
|
||||
after = PortalImage.find_by(id: params[:after_id])
|
||||
|
||||
Admins::DragPortalImageService.call(move, after)
|
||||
render_ok
|
||||
rescue Admins::DragPortalImageService::Error => e
|
||||
render_error(e.message)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_image
|
||||
@_current_image ||= PortalImage.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,32 @@
|
||||
class Admins::LaboratoriesController < Admins::BaseController
|
||||
def index
|
||||
params[:sort_by] = params[:sort_by].presence || 'id'
|
||||
params[:sort_direction] = params[:sort_direction].presence || 'desc'
|
||||
|
||||
laboratories = Admins::LaboratoryQuery.call(params)
|
||||
@laboratories = paginate laboratories.preload(:school, :laboratory_users)
|
||||
end
|
||||
|
||||
def create
|
||||
Admins::CreateLaboratoryService.call(create_params)
|
||||
render_ok
|
||||
rescue Admins::CreateLaboratoryService::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
|
||||
def destroy
|
||||
current_laboratory.destroy!
|
||||
|
||||
render_delete_success
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_laboratory
|
||||
@_current_laboratory ||= Laboratory.find(params[:id])
|
||||
end
|
||||
|
||||
def create_params
|
||||
params.permit(:school_id)
|
||||
end
|
||||
end
|
@ -0,0 +1,20 @@
|
||||
class Admins::LaboratorySettingsController < Admins::BaseController
|
||||
def show
|
||||
@laboratory = current_laboratory
|
||||
end
|
||||
|
||||
def update
|
||||
Admins::SaveLaboratorySettingService.call(current_laboratory, form_params)
|
||||
render_ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_laboratory
|
||||
@_current_laboratory ||= Laboratory.find(params[:laboratory_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,19 @@
|
||||
class Admins::LaboratoryUsersController < Admins::BaseController
|
||||
helper_method :current_laboratory
|
||||
|
||||
def create
|
||||
Admins::AddLaboratoryUserService.call(current_laboratory, params.permit(user_ids: []))
|
||||
current_laboratory.reload
|
||||
end
|
||||
|
||||
def destroy
|
||||
@laboratory_user = current_laboratory.laboratory_users.find_by(user_id: params[:user_id])
|
||||
@laboratory_user.destroy! if @laboratory_user.present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_laboratory
|
||||
@_current_laboratory ||= Laboratory.find(params[:laboratory_id])
|
||||
end
|
||||
end
|
@ -0,0 +1,15 @@
|
||||
module LaboratoryHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
helper_method :default_setting
|
||||
end
|
||||
|
||||
def current_laboratory
|
||||
@_current_laboratory ||= (Laboratory.find_by_subdomain(request.subdomain) || Laboratory.find(1))
|
||||
end
|
||||
|
||||
def default_setting
|
||||
@_default_setting ||= LaboratorySetting.find_by(laboratory_id: 1)
|
||||
end
|
||||
end
|
@ -0,0 +1,6 @@
|
||||
class LibraryTagsController < ApplicationController
|
||||
def index
|
||||
library_tags = LibraryTag.all
|
||||
render_ok(library_tags: library_tags.as_json(only: %i[id name]), count: library_tags.size)
|
||||
end
|
||||
end
|
@ -0,0 +1,5 @@
|
||||
class SettingsController < ApplicationController
|
||||
def show
|
||||
@laboratory = current_laboratory
|
||||
end
|
||||
end
|
@ -0,0 +1,10 @@
|
||||
class SubjectListsController < ApplicationController
|
||||
def index
|
||||
@results = SubjectSearchService.call(search_params)
|
||||
end
|
||||
|
||||
private
|
||||
def search_params
|
||||
params.permit(:keyword, :type, :page, :limit, :order, :sort)
|
||||
end
|
||||
end
|
@ -0,0 +1,5 @@
|
||||
class TemplatesController < ApplicationController
|
||||
def show
|
||||
@template = EcTemplate.find_by_name!(params[:name])
|
||||
end
|
||||
end
|
@ -1,6 +1,6 @@
|
||||
module MemosHelper
|
||||
|
||||
def forum_list
|
||||
[{id: 5, name: "技术分享"}, {id: 3, name: "操作指南"}]
|
||||
[{id: 5, name: "技术分享"}, {id: 3, name: "操作指南"}, {id: 16, name: "通知公告"}]
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,2 @@
|
||||
module SubjectListsHelper
|
||||
end
|
@ -0,0 +1,22 @@
|
||||
# 删除课堂用户
|
||||
class CourseDeleteStudentNotifyJob < ApplicationJob
|
||||
queue_as :notify
|
||||
|
||||
def perform(course_id, student_ids, trigger_user_id)
|
||||
course = Course.find_by(id: course_id)
|
||||
return if course.blank?
|
||||
|
||||
attrs = %i[user_id trigger_user_id container_id container_type belong_container_id
|
||||
belong_container_type tiding_type created_at updated_at]
|
||||
|
||||
same_attrs = {
|
||||
trigger_user_id: trigger_user_id, container_id: course.id, container_type: 'DeleteCourseMember',
|
||||
belong_container_id: course.id, belong_container_type: 'Course', tiding_type: 'System'
|
||||
}
|
||||
Tiding.bulk_insert(*attrs) do |worker|
|
||||
student_ids.each do |user_id|
|
||||
worker.add same_attrs.merge(user_id: user_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,12 @@
|
||||
class CreateDiffRecordJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user_id, obj_id, obj_klass, column_name, before, after)
|
||||
user = User.find_by(id: user_id)
|
||||
obj = obj_klass.constantize.find_by(id: obj_id)
|
||||
|
||||
return if user.blank? || obj.blank?
|
||||
|
||||
CreateDiffRecordService.call(user, obj, column_name, before, after)
|
||||
end
|
||||
end
|
@ -0,0 +1,30 @@
|
||||
class EndExerciseCalculateJob < ApplicationJob
|
||||
|
||||
include ExercisesHelper
|
||||
include GitHelper
|
||||
|
||||
queue_as :default
|
||||
|
||||
def perform(ex_user_ids,exercise)
|
||||
exercise_users = ExerciseUser.where(id: ex_user_ids)
|
||||
exercise_users.each do |user|
|
||||
if user.commit_status == 0 && user.start_at.present?
|
||||
objective_score = calculate_student_score(exercise,user.user)[:total_score]
|
||||
user_sub_score = user.subjective_score
|
||||
subjective_score = user_sub_score < 0.0 ? 0.0 : user_sub_score
|
||||
total_score = objective_score + subjective_score
|
||||
commit_option = {
|
||||
:status => 1,
|
||||
:commit_status => 1,
|
||||
:end_at => Time.now,
|
||||
:objective_score => objective_score,
|
||||
:score => total_score,
|
||||
:subjective_score => user_sub_score,
|
||||
:commit_method => user&.commit_method.to_i > 0 ? user&.commit_method.to_i : 4
|
||||
}
|
||||
user.update_attributes(commit_option)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,33 @@
|
||||
class ResubmitStudentWorkNotifyJob < ApplicationJob
|
||||
queue_as :notify
|
||||
|
||||
def perform(homework_id, student_ids)
|
||||
homework = HomeworkCommon.find_by(id: homework_id)
|
||||
return if homework.blank? || student_ids.blank?
|
||||
course = homework.course
|
||||
|
||||
attrs = %i[user_id trigger_user_id container_id container_type parent_container_id parent_container_type
|
||||
belong_container_id belong_container_type tiding_type viewed created_at updated_at]
|
||||
|
||||
same_attrs = {
|
||||
container_type: 'ResubmitStudentWork', parent_container_id: homework.id, parent_container_type: 'HomeworkCommon',
|
||||
belong_container_id: course.id, belong_container_type: 'Course', tiding_type: 'HomeworkCommon', viewed: 0
|
||||
}
|
||||
Tiding.bulk_insert(*attrs) do |worker|
|
||||
student_ids.each do |user_id|
|
||||
next unless User.exists?(id: user_id)
|
||||
|
||||
work = homework.student_works.find_by(user_id: user_id)
|
||||
next if work.blank?
|
||||
score_user_ids = work.student_works_scores.where.not(score: nil).where(reviewer_role: [1, 2]).pluck(user_id).uniq
|
||||
next if score_user_ids.blank?
|
||||
|
||||
attrs = same_attrs.merge(trigger_user_id: user_id, container_id: work.id)
|
||||
|
||||
score_user_ids.each do |user_id|
|
||||
worker.add attrs.merge(user_id: user_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,75 +0,0 @@
|
||||
module Util::Wechat
|
||||
BASE_SITE = 'https://api.weixin.qq.com'.freeze
|
||||
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
def js_sdk_signature(url, noncestr, timestamp)
|
||||
data = { jsapi_ticket: jsapi_ticket, noncestr: noncestr, timestamp: timestamp, url: url }
|
||||
str = data.map { |k, v| "#{k}=#{v}" }.join('&')
|
||||
Digest::SHA1.hexdigest(str)
|
||||
end
|
||||
|
||||
def access_token
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(access_token_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/token', appid: appid, secret: secret, grant_type: 'client_credential')
|
||||
result['access_token']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_access_token
|
||||
Rails.cache.delete(access_token_cache_key)
|
||||
access_token
|
||||
end
|
||||
|
||||
def jsapi_ticket
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(jsapi_ticket_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/ticket/getticket', access_token: access_token, type: 'jsapi')
|
||||
result['ticket']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_jsapi_ticket
|
||||
Rails.cache.delete(jsapi_ticket_cache_key)
|
||||
jsapi_ticket
|
||||
end
|
||||
|
||||
def access_token_cache_key
|
||||
"#{base_cache_key}/access_token"
|
||||
end
|
||||
|
||||
def jsapi_ticket_cache_key
|
||||
"#{base_cache_key}/jsapi_ticket"
|
||||
end
|
||||
|
||||
def base_cache_key
|
||||
"wechat/#{appid}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(method, url, **params)
|
||||
Rails.logger.error("[wechat] request: #{method} #{url} #{params.inspect}")
|
||||
|
||||
client = Faraday.new(url: BASE_SITE)
|
||||
response = client.public_send(method, url, params)
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
Rails.logger.error("[wechat] response:#{response.status} #{result.inspect}")
|
||||
|
||||
if response.status != 200
|
||||
raise Error, result.inspect
|
||||
end
|
||||
|
||||
if result['errcode'].present? && result['errcode'].to_i.nonzero?
|
||||
raise Error, result.inspect
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,2 @@
|
||||
module Wechat
|
||||
end
|
@ -0,0 +1,11 @@
|
||||
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,74 @@
|
||||
class Wechat::Client
|
||||
BASE_SITE = 'https://api.weixin.qq.com'.freeze
|
||||
|
||||
attr_reader :appid, :secret
|
||||
|
||||
def initialize(appid, secret)
|
||||
@appid = appid
|
||||
@secret = secret
|
||||
end
|
||||
|
||||
def access_token
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(access_token_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/token', appid: appid, secret: secret, grant_type: 'client_credential')
|
||||
result['access_token']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_access_token
|
||||
Rails.cache.delete(access_token_cache_key)
|
||||
access_token
|
||||
end
|
||||
|
||||
def jsapi_ticket
|
||||
# 7200s 有效时间
|
||||
Rails.cache.fetch(jsapi_ticket_cache_key, expires_in: 100.minutes) do
|
||||
result = request(:get, '/cgi-bin/ticket/getticket', access_token: access_token, type: 'jsapi')
|
||||
result['ticket']
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_jsapi_ticket
|
||||
Rails.cache.delete(jsapi_ticket_cache_key)
|
||||
jsapi_ticket
|
||||
end
|
||||
|
||||
def jscode2session(code)
|
||||
request(:get, '/sns/jscode2session', appid: appid, secret: secret, js_code: code, grant_type: 'authorization_code')
|
||||
end
|
||||
|
||||
def access_token_cache_key
|
||||
"#{base_cache_key}/access_token"
|
||||
end
|
||||
|
||||
def jsapi_ticket_cache_key
|
||||
"#{base_cache_key}/jsapi_ticket"
|
||||
end
|
||||
|
||||
def base_cache_key
|
||||
"wechat/#{appid}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(method, url, **params)
|
||||
Rails.logger.error("[wechat] request: #{method} #{url} #{params.except(:secret).inspect}")
|
||||
|
||||
client = Faraday.new(url: BASE_SITE)
|
||||
response = client.public_send(method, url, params)
|
||||
result = JSON.parse(response.body)
|
||||
|
||||
Rails.logger.error("[wechat] response:#{response.status} #{result.inspect}")
|
||||
|
||||
if response.status != 200
|
||||
raise Wechat::Error.parse(result)
|
||||
end
|
||||
|
||||
if result['errcode'].present? && result['errcode'].to_i.nonzero?
|
||||
raise Wechat::Error.parse(result)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
class Wechat::Error < StandardError
|
||||
attr_reader :code
|
||||
|
||||
def initialize(code, message)
|
||||
super message
|
||||
@code = code
|
||||
end
|
||||
|
||||
class << self
|
||||
def parse(result)
|
||||
new(result['errcode'], result['errmsg'])
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,17 @@
|
||||
class Wechat::OfficialAccount
|
||||
class << self
|
||||
attr_accessor :appid, :secret
|
||||
|
||||
delegate :access_token, :jsapi_ticket, to: :client
|
||||
|
||||
def js_sdk_signature(url, noncestr, timestamp)
|
||||
data = { jsapi_ticket: jsapi_ticket, noncestr: noncestr, timestamp: timestamp, url: url }
|
||||
str = data.map { |k, v| "#{k}=#{v}" }.join('&')
|
||||
Digest::SHA1.hexdigest(str)
|
||||
end
|
||||
|
||||
def client
|
||||
@_client ||= Wechat::Client.new(appid, secret)
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
class DiffRecord < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :container, polymorphic: true
|
||||
|
||||
has_one :diff_record_content, dependent: :destroy
|
||||
|
||||
delegate :content, to: :diff_record_content
|
||||
end
|
@ -0,0 +1,3 @@
|
||||
class DiffRecordContent < ApplicationRecord
|
||||
belongs_to :diff_record
|
||||
end
|
@ -1,2 +1,3 @@
|
||||
class Forum < ApplicationRecord
|
||||
has_many :memos, dependent: :destroy
|
||||
end
|
||||
|
@ -0,0 +1,26 @@
|
||||
class Laboratory < ApplicationRecord
|
||||
belongs_to :school, optional: true
|
||||
|
||||
has_many :laboratory_users, dependent: :destroy
|
||||
has_many :users, through: :laboratory_users, source: :user
|
||||
|
||||
has_one :laboratory_setting, dependent: :destroy
|
||||
|
||||
validates :identifier, uniqueness: { case_sensitive: false }, allow_nil: true
|
||||
|
||||
def site
|
||||
rails_env = EduSetting.get('rails_env')
|
||||
suffix = rails_env && rails_env != 'production' ? ".#{rails_env}.educoder.net" : '.educoder.net'
|
||||
|
||||
identifier ? "#{identifier}#{suffix}" : ''
|
||||
end
|
||||
|
||||
def self.find_by_subdomain(subdomain)
|
||||
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
|
||||
|
||||
find_by_identifier(subdomain)
|
||||
end
|
||||
end
|
@ -0,0 +1,54 @@
|
||||
class LaboratorySetting < ApplicationRecord
|
||||
belongs_to :laboratory
|
||||
|
||||
serialize :config, JSON
|
||||
|
||||
%i[name navbar footer].each do |method_name|
|
||||
define_method method_name do
|
||||
config&.[](method_name.to_s)
|
||||
end
|
||||
|
||||
define_method "#{method_name}=" do |value|
|
||||
self.config ||= {}
|
||||
config.[]=(method_name.to_s, value)
|
||||
end
|
||||
end
|
||||
|
||||
def login_logo_url
|
||||
logo_url('login')
|
||||
end
|
||||
|
||||
def nav_logo_url
|
||||
logo_url('nav')
|
||||
end
|
||||
|
||||
def tab_logo_url
|
||||
logo_url('tab')
|
||||
end
|
||||
|
||||
def default_navbar
|
||||
self.class.default_config[:navbar]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def logo_url(type)
|
||||
return nil unless Util::FileManage.exists?(self, type)
|
||||
Util::FileManage.source_disk_file_url(self, type)
|
||||
end
|
||||
|
||||
def self.default_config
|
||||
{
|
||||
name: nil,
|
||||
navbar: [
|
||||
{ 'name' => '实践课程', 'link' => '/paths', 'hidden' => false },
|
||||
{ 'name' => '翻转课堂', 'link' => '/courses', 'hidden' => false },
|
||||
{ 'name' => '实现项目', 'link' => '/shixuns', 'hidden' => false },
|
||||
{ 'name' => '在线竞赛', 'link' => '/competitions', 'hidden' => false },
|
||||
{ 'name' => '教学案例', 'link' => '/moop_cases', 'hidden' => false },
|
||||
{ 'name' => '交流问答', 'link' => '/forums', 'hidden' => false },
|
||||
],
|
||||
footer: nil
|
||||
}
|
||||
end
|
||||
end
|
@ -0,0 +1,4 @@
|
||||
class LaboratoryUser < ApplicationRecord
|
||||
belongs_to :laboratory
|
||||
belongs_to :user
|
||||
end
|
@ -1,2 +1,5 @@
|
||||
class PortalImage < ApplicationRecord
|
||||
def online?
|
||||
status?
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,23 @@
|
||||
class Admins::LaboratoryQuery < ApplicationQuery
|
||||
include CustomSortable
|
||||
|
||||
attr_reader :params
|
||||
|
||||
sort_columns :id, default_by: :id, default_direction: :desc
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
laboratories = Laboratory.all
|
||||
|
||||
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}%")
|
||||
end
|
||||
|
||||
custom_sort laboratories, params[:sort_by], params[:sort_direction]
|
||||
end
|
||||
end
|
@ -0,0 +1,19 @@
|
||||
class Admins::AddLaboratoryUserService < ApplicationService
|
||||
attr_reader :laboratory, :params
|
||||
|
||||
def initialize(laboratory, params)
|
||||
@laboratory = laboratory
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
columns = %i[]
|
||||
LaboratoryUser.bulk_insert(*columns) do |worker|
|
||||
Array.wrap(params[:user_ids]).compact.each do |user_id|
|
||||
next if laboratory.laboratory_users.exists?(user_id: user_id)
|
||||
|
||||
worker.add(laboratory_id: laboratory.id, user_id: user_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue