Merge branches 'dev_aliyun' and 'dev_ysm' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_ysm

problem_set
杨树明 5 years ago
commit 29ba5814af

@ -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,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,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;
}
}
}
}

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

@ -338,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?

@ -26,7 +26,7 @@ class Cooperative::BaseController < ApplicationController
def laboratory_exist!
return if current_laboratory.present?
redirect_to '/nopage'
redirect_to '/403'
end
def require_login
@ -48,7 +48,7 @@ class Cooperative::BaseController < ApplicationController
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')
path = Rails.root.join('app/views/cooperative/shared/after_render_js_hook.js.erb')
return unless File.exists?(path)
append_js = ERB.new(File.open(path).read).result

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

@ -0,0 +1,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

@ -14,7 +14,7 @@ class ShixunsController < ApplicationController
before_action :shixun_access_allowed, except: [:index, :new, :create, :menus, :get_recommend_shixuns,
:propaedeutics, :departments, :apply_shixun_mirror,
:get_mirror_script, :download_file, :shixun_list]
:get_mirror_script, :download_file, :shixun_list, :review_shixuns]
before_action :find_repo_name, only: [:repository, :commits, :file_content, :update_file, :shixun_exec, :copy, :add_file]
before_action :allowed, only: [:update, :close, :update_propaedeutics, :settings, :publish,
@ -986,6 +986,21 @@ class ShixunsController < ApplicationController
@shixun.update_column(:status, 0)
end
# 创建实训审核
def review_shixun
validate_review_shixun_params
# 没有记录就创建记录, 如果有记录就
@shixun.shixun_reviews.create!(user_id: current_user.id, status: params[:status],
review_type: params[:review_type], evaluate_content: params[:evaluate_content])
normal_status("审核完成")
end
# 实训审核最新记录
def review_newest_record
@content_record = @shixun.shixun_reviews.where(review_type: "Content").first
@perfer_record = @shixun.shixun_reviews.where(review_type: "Performance").first
end
private
def shixun_params
raise("实训名称不能为空") if params[:shixun][:name].blank?
@ -994,6 +1009,11 @@ private
:hide_code, :forbid_copy, :vnc_evaluate, :code_edit_permission)
end
def validate_review_shixun_params
tip_exception("只有平台管理员或运营人员才能审核") if !admin_or_business?
tip_exception("审核类型参数不对") unless ["Content", "Performance"].include?(params[:review_type])
end
def shixun_info_params
raise("实训描述不能为空") if params[:shixun_info][:description].blank?
raise("评测脚本不能为空") if params[:shixun_info][:evaluate_script].blank?

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

@ -9,7 +9,7 @@ class Weapps::CodeSessionsController < Weapps::BaseController
set_weapp_session_key(result['session_key']) # weapp session_key写入缓存 后续解密需要
# 已授权,绑定过账号
open_user = OpenUser::Wechat.find_by(uid: result['unionid'])
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)

@ -3,6 +3,8 @@ class OpenUser < ApplicationRecord
validates :uid, presence: true, uniqueness: { scope: :type }
serialize :extra, JSON
def can_bind_cache_key
"open_user:#{type}:#{uid}:can_bind"
end

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

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

@ -47,6 +47,9 @@ class Shixun < ApplicationRecord
has_many :shixun_service_configs, :dependent => :destroy
has_many :tidings, as: :container, dependent: :destroy
# 实训审核记录
has_many :shixun_reviews, -> {order("challenges.created_at desc")}, :dependent => :destroy
scope :search_by_name, ->(keyword) { where("name like ? or description like ? ",
"%#{keyword}%", "%#{keyword}%") }

@ -0,0 +1,4 @@
class ShixunReview < ApplicationRecord
belongs_to :user
belongs_to :shixun
end

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

@ -28,7 +28,7 @@ class Oauth::CreateOrFindQqAccountService < ApplicationService
user.create_user_extension!(gender: gender)
end
new_open_user = OpenUsers::QQ.create!(user: user, uid: params['uid'])
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

@ -39,7 +39,7 @@ class Oauth::CreateOrFindWechatAccountService < ApplicationService
Util.download_file(result['headimgurl'], avatar_path)
end
new_open_user= OpenUsers::Wechat.create!(user: user, uid: result['unionid'])
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

@ -17,7 +17,7 @@
</div>
<%= text_field_tag :identifier, @laboratory.identifier,
maxlength: 15, class: 'form-control font-16',
'onKeyUp': 'value=value.replace(/[^\w\.\-\/]/ig,"").toLowerCase()',
'onKeyUp': 'value=value.replace(/[^\w\-\/]/ig,"").toLowerCase()',
style: 'text-transform:lowercase'%>
<div class="input-group-append">
<% rails_env = EduSetting.get('rails_env') %>

@ -6,6 +6,7 @@
<th width="8%">头像</th>
<th width="14%">创建者</th>
<th width="28%" class="text-left">实训名称</th>
<th width="10">审核情况(内容/性能)</th>
<th width="12%">任务数</th>
<th width="16%">时间</th>
<% if is_processed %>
@ -21,6 +22,8 @@
<% applies.each do |apply| %>
<% user = apply.user %>
<% shixun = shixun_map[apply.container_id] %>
<% content_review = shixun.shixun_reviews.select{|sr| sr.review_type == 'Content'}.first %>
<% perference_review = shixun.shixun_reviews.select{|sr| sr.review_type == 'Performance'}.first %>
<tr class="shixun-authorization-item shixun-authorization-<%= apply.id %>">
<td>
<%= link_to "/users/#{user.login}", class: 'shixun-authorization-avatar', target: '_blank', data: { toggle: 'tooltip', title: '个人主页' } do %>
@ -33,6 +36,10 @@
<%= overflow_hidden_span shixun.name, width: 300 %>
<% end %>
</td>
<td>
<%= check_box_tag :content, content_review&.status, content_review&.status.to_i == 1, class:"shixun-setting-form" ,title:"内容审核"%>
<%= check_box_tag :perference, perference_review&.status, perference_review&.status.to_i == 1, class:"shixun-setting-form" ,title:"性能审核"%>
</td>
<td><%= shixun.challenges_count %></td>
<td><%= apply.updated_at.strftime('%Y-%m-%d %H:%M') %></td>

@ -0,0 +1,41 @@
<% define_breadcrumbs do %>
<% add_breadcrumb('轮播图设置') %>
<% end %>
<div class="card mb-5 carousels-card">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="flex-1">首页轮播图<span class="text-secondary font-12">(拖动排序)</span></span>
<%= javascript_void_link '添加', class: 'btn btn-primary btn-sm add-btn', data: { toggle: 'modal', target: '.cooperative-add-carousel-modal' } %>
</div>
<div class="card-body row" id="carousels-container">
<% @images.each_with_index do |image, index| %>
<div class="col-12 custom-carousel-item custom-carousel-item-<%= image.id %>" data-id="<%= image.id %>">
<div class="border rounded relative p-3 mb-3 drag row align-items-center <%= image.online? ? '' : 'not_active' %>">
<div class="col-2 col-md-1 custom-carousel-item-no"><%= index + 1 %></div>
<div class="col-10 col-md-3 custom-carousel-item-img" data-source-id="<%= image.id %>" data-source-type="PortalImage" data-toggle="modal" data-target=".admin-upload-file-modal">
<img src="<%= Util::FileManage.exist?('PortalImage', image.id) ? Util::FileManage.disk_file_url('PortalImage', image.id) : '' %>" data-toggle="tooltip" data-title="重新上传"/>
</div>
<div class="col-10 col-md-7">
<div class="input-group">
<input type="text" value="<%= image.name %>" class="form-control name-input" placeholder="请输入名称" />
<input type="text" value="<%= image.link %>" class="form-control link-input" placeholder="请输入跳转地址">
<div class="input-group-prepend">
<button class="input-group-text save-data-btn" data-id="<%= image.id %>">保存</button>
</div>
</div>
</div>
<div class="col-2 col-md-1 operate-box">
<%= check_box_tag(:online, 1, image.online?, id: nil, class: 'online-check-box', data: { id: image.id, toggle: 'tooltip', title: '首页展示' }) %>
<%= delete_link '删除', cooperative_carousel_path(image, laboratory_id: image.laboratory_id, element: ".custom-carousel-item-#{image.id}", not_refresh: true), class: 'delete-btn' do %>
<i class="fa fa-trash-o" data-id="<%= image.id %>"></i>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<%= render partial: 'cooperative/carousels/shared/add_carousel_modal', locals: { laboratory_id: current_laboratory } %>
<%= render partial: 'cooperative/shared/modal/upload_file_modal' %>

@ -0,0 +1,36 @@
<div class="modal fade cooperative-add-carousel-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加轮播图</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<%= simple_form_for(PortalImage.new, url: cooperative_carousels_path(laboratory_id: laboratory_id), html: { class: 'cooperative-add-carousel-form', enctype: 'multipart/form-data' }) do |f| %>
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">图片</span>
</div>
<div class="custom-file flex-row-reverse">
<input type="file" name="portal_image[image]" class="img-file-input" id="img-file-input" accept="image/*">
<label class="custom-file-label file-names" for="img-file-input">选择文件</label>
</div>
</div>
</div>
<%= f.input :name, label: '名称', placeholder: '请输入名称' %>
<%= f.input :link, as: :url, label: '跳转地址', placeholder: '请输入跳转地址' %>
<div class="error text-danger"></div>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -16,7 +16,7 @@
</div>
<%= text_field_tag :identifier, @laboratory.identifier,
maxlength: 15, class: 'form-control font-16',
'onKeyUp': 'value=value.replace(/[^\w\.\-\/]/ig,"").toLowerCase()',
'onKeyUp': 'value=value.replace(/[^\w\-\/]/ig,"").toLowerCase()',
style: 'text-transform:lowercase'%>
<div class="input-group-append">
<% rails_env = EduSetting.get('rails_env') %>

@ -0,0 +1,14 @@
<% define_breadcrumbs do %>
<% add_breadcrumb('管理员列表', cooperative_laboratory_users_path) %>
<% end %>
<div class="box search-form-container laboratory-user-list-form">
<div></div>
<%= javascript_void_link '添加管理员', class: 'btn btn-primary btn-sm', data: { toggle: 'modal', target: '.cooperative-add-laboratory-user-modal'} %>
</div>
<div class="box laboratory-user-list-container">
<%= render partial: 'cooperative/laboratory_users/shared/list', locals: { laboratory_users: @laboratory_users } %>
</div>
<%= render 'cooperative/laboratory_users/shared/add_laboratory_user_modal' %>

@ -0,0 +1 @@
$('.laboratory-user-list-container').html("<%= j(render partial: 'cooperative/laboratory_users/shared/list', locals: { laboratory_users: @laboratory_users }) %>");

@ -0,0 +1,28 @@
<div class="modal fade cooperative-add-laboratory-user-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加管理员</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="cooperative-add-laboratory-user-form">
<div class="form-group d-flex">
<label class="col-form-label">管理员:</label>
<div class="d-flex flex-column-reverse w-75">
<select id="user_ids" name="user_ids" class="form-control laboratory-user-select"></select>
</div>
</div>
<div class="error text-danger"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,43 @@
<table class="table table-hover laboratory-user-list-table">
<thead class="thead-light">
<tr>
<th width="8%">头像</th>
<th width="12%" class="text-left">真实姓名</th>
<th width="20%">邮件地址</th>
<th width="20%">手机号码</th>
<th width="30%">单位部门</th>
<th width="10%">操作</th>
</tr>
</thead>
<tbody>
<% if laboratory_users.present? %>
<% laboratory_users.each do |laboratory_user| %>
<% user = laboratory_user.user %>
<tr class="laboratory-user-item-<%= laboratory_user.id %>">
<td class="text-left">
<%= link_to "/users/#{user.login}", target: '_blank' do %>
<img src="/images/<%= url_to_avatar(user) %>" class="rounded-circle" width="40" height="40" />
<% end %>
</td>
<td class="text-left">
<%= link_to "/users/#{user.login}", target: '_blank' do %>
<%= overflow_hidden_span user.real_name, width: 100 %>
<% end %>
</td>
<td><%= overflow_hidden_span display_text(user.mail), width: 150 %></td>
<td><%= overflow_hidden_span display_text(user.phone), width: 100 %></td>
<td><%= [user.school_name.presence, user.department_name.presence].compact.join('-') %></td>
<td class="action-container">
<% if current_user.id != laboratory_user.user_id %>
<%= delete_link '删除', cooperative_laboratory_user_path(laboratory_user, element: ".laboratory-user-item-#{laboratory_user.id}"), class: 'delete-laboratory-user-action' %>
<% end %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'cooperative/shared/paginate', locals: { objects: laboratory_users } %>

@ -3,7 +3,7 @@
<div class="sidebar-header">
<a href="/" class="sidebar-header-logo" data-toggle="tooltip" title="返回主页">
<img src="<%= current_setting_or_default(:nav_logo_url) %>"/>
<span class="logo-label">管理中心</span>
<span class="logo-label">后台管理</span>
</a>
<div id="sidebarCollapse" class="navbar-btn <%= sidebar_collapse ? 'active' : '' %>">
<i class="fa fa-chevron-left fold" data-toggle="tooltip" data-placement="right" data-boundary="window" title="收起"></i>
@ -13,8 +13,10 @@
<!-- Sidebar Links -->
<ul class="list-unstyled components">
<li><%= sidebar_item(cooperative_path, '概览', icon: 'dashboard', controller: 'cooperative-dashboards') %></li>
<!-- <li><%#= sidebar_item(cooperative_path, '概览', icon: 'dashboard', controller: 'cooperative-dashboards') %></li>-->
<li><%= sidebar_item(edit_cooperative_laboratory_setting_path, '网站设置', icon: 'cogs', controller: 'cooperative-laboratory_settings') %></li>
<li><%= sidebar_item(cooperative_carousels_path, '轮播图设置', icon: 'image', controller: 'cooperative-carousels') %></li>
<li><%= sidebar_item(cooperative_laboratory_users_path, '管理员列表', icon: 'user', controller: 'cooperative-laboratory_users') %></li>
<li><%= sidebar_item('/', '返回主页', icon: 'sign-out', controller: 'root') %></li>
</ul>
</nav>

@ -1,4 +1,4 @@
<div class="modal fade admin-upload-file-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal fade cooperative-upload-file-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
@ -8,7 +8,7 @@
</button>
</div>
<div class="modal-body">
<form class="admin-upload-file-form" enctype="multipart/form-data">
<form class="cooperative-upload-file-form" enctype="multipart/form-data">
<%= hidden_field_tag(:source_type, nil) %>
<%= hidden_field_tag(:source_id, nil) %>
<div class="input-group">

@ -0,0 +1,6 @@
json.count @users.total_count
json.users do
json.array! @users.each do |user|
json.extract! user, :id, :login, :real_name, :identity, :school_name
end
end

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title><%= current_laboratory.school&.name %>-后台管理</title>
<title><%= current_laboratory.school&.name || 'EduCoder' %>-后台管理</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel='shortcut icon' type='image/x-icon' href='<%= current_setting_or_default(:tab_logo_url) %>' />

@ -0,0 +1,16 @@
if @content_record
json.content_info @content_record do
json.status @content_record.status
json.time format_time(@content_record.created_at)
json.username @content_record.user&.real_name
end
end
if @perfer_record
json.perference_info @perfer_record do
json.status @perfer_record.status
json.time format_time(@perfer_record.created_at)
json.username @perfer_record.user&.real_name
end
end

@ -27,3 +27,9 @@ json.base_info_completed user.profile_completed?
json.all_certified user.all_certified?
json.has_password user.hashed_password.present?
json.open_users do
json.array! user.open_users do |open_user|
json.extract! open_user, :id, :en_type, :nickname
end
end

@ -39,6 +39,9 @@ json.top do
json.college_identifier @user.college_identifier
# 旧版的域名
json.old_url @old_domain
# 云上实验室管理权限
json.laboratory_user current_laboratory.laboratory_users.exists?(user_id: @user&.id) || @user&.admin_or_business?
end
json.down do

@ -116,6 +116,7 @@ Rails.application.routes.draw do
resource :auth_attachment, only: [:create]
resource :authentication_apply, only: [:create]
resource :professional_auth_apply, only: [:create]
resources :open_users, only: [:destroy]
end
end
end
@ -220,6 +221,8 @@ Rails.application.routes.draw do
get :cancel_publish
get :publish
get :shixun_exec
post :review_shixun
get :review_newest_record
end
resources :challenges do
@ -1006,9 +1009,12 @@ Rails.application.routes.draw do
end
namespace :cooperative do
get '/', to: 'dashboards#show'
# get '/', to: 'dashboards#show'
get '/', to: 'laboratory_settings#edit'
resources :users, only: [:index]
resources :laboratory_users, only: [:index, :create, :destroy]
resource :laboratory_setting, only: [:edit, :update]
resource :carousels, only: [:index, :create, :update, :destroy] do
resources :carousels, only: [:index, :create, :update, :destroy] do
post :drag, on: :collection
end
end

@ -0,0 +1,5 @@
class AddExtraToOpenUsers < ActiveRecord::Migration[5.2]
def change
add_column :open_users, :extra, :text
end
end

@ -0,0 +1,12 @@
class CreateShixunReviews < ActiveRecord::Migration[5.2]
def change
create_table :shixun_reviews do |t|
t.references :user
t.references :shixun
t.string :evaluate_content
t.integer :status
t.string :review_type
t.timestamps
end
end
end

File diff suppressed because one or more lines are too long

@ -133823,6 +133823,132 @@ module.exports = tick;
!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var n=jQuery.fn.select2.amd;n.define("select2/i18n/zh-CN",[],function(){return{errorLoading:function(){return"无法载入结果。"},inputTooLong:function(n){return"请删除"+(n.input.length-n.maximum)+"个字符"},inputTooShort:function(n){return"请再输入至少"+(n.minimum-n.input.length)+"个字符"},loadingMore:function(){return"载入更多结果…"},maximumSelected:function(n){return"最多只能选择"+n.maximum+"个项目"},noResults:function(){return"未找到结果"},searching:function(){return"搜索中…"},removeAllItems:function(){return"删除所有项目"}}}),n.define,n.require}();
$(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);
})
}
})
;
$(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');
@ -133909,6 +134035,130 @@ $(document).on('turbolinks:load', function() {
})
}
});
$(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');
}
});
}
});
$(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);
}
});
}
});
}
});
$(document).on('turbolinks:load', function(){
$('#sidebarCollapse').on('click', function () {
$(this).toggleClass('active');

@ -24862,6 +24862,78 @@ input.form-control {
display: block;
}
/* line 4, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
/* line 10, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item-no {
font-size: 28px;
text-align: center;
}
/* line 15, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item-img {
cursor: pointer;
width: 100%;
height: 60px;
}
/* line 20, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item-img > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
/* line 28, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item .not_active {
background: #F0F0F0;
}
/* line 32, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item .delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
/* line 38, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item .save-url-btn {
cursor: pointer;
}
/* line 42, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item .operate-box {
display: -webkit-box;
display: flex;
-webkit-box-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
align-items: center;
}
/* line 48, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item .online-check-box {
font-size: 20px;
}
/* line 52, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item .name-input {
-webkit-box-flex: 1;
flex: 1;
}
/* line 55, app/assets/stylesheets/cooperative/carousels.scss */
.cooperative-carousels-index-page .carousels-card .custom-carousel-item .link-input {
-webkit-box-flex: 3;
flex: 3;
}
/* line 1, app/assets/stylesheets/cooperative/common.scss */
.cooperative-body-container {
padding: 20px;

@ -1215,6 +1215,10 @@ submittojoinclass=(value)=>{
this.props.Headertop && this.props.Headertop.college_identifier &&
<li><a href={`${this.props.Headertop.old_url}/colleges/${this.props.Headertop.college_identifier}/statistics`}>学院统计</a></li>
}
{
this.props.Headertop && this.props.Headertop.laboratory_user &&
<li><a href="/cooperative">后台管理</a></li>
}
<li><a href={`/account/profile`}>账号管理</a></li>
{/*<li><a onClick={()=>this.educoderlogin()} >登入测试接口</a></li>*/}

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe ShixunReview, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end
Loading…
Cancel
Save