admins: laboratory manage

dev_ec
p31729568 5 years ago
parent a83d126ce6
commit 9b8a20311c

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

@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
include ControllerRescueHandler
include GitHelper
include LoggerHelper
include LaboratoryHelper
protect_from_forgery prepend: true, unless: -> { request.format.json? }

@ -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,5 @@
class SettingsController < ApplicationController
def show
@laboratory = current_laboratory
end
end

@ -10,31 +10,35 @@ module Util::FileManage
File.join(Rails.root, "public", "images", relative_path)
end
def disk_filename(source_type, source_id,image_file=nil)
File.join(storage_path, "#{source_type}", "#{source_id}")
def disk_filename(source_type, source_id, suffix=nil)
File.join(storage_path, "#{source_type}", "#{source_id}#{suffix}")
end
def exist?(source_type, source_id)
File.exist?(disk_filename(source_type, source_id))
def source_disk_filename(source, suffix=nil)
disk_filename(source.class.name, source.id, suffix)
end
def exists?(source)
File.exist?(disk_filename(source.class, source.id))
def exist?(source_type, source_id, suffix=nil)
File.exist?(disk_filename(source_type, source_id, suffix))
end
def exists?(source, suffix=nil)
File.exist?(disk_filename(source.class, source.id, suffix))
end
def disk_file_url(source_type, source_id, suffix = nil)
t = ctime(source_type, source_id)
t = ctime(source_type, source_id, suffix)
File.join('/images', relative_path, "#{source_type}", "#{source_id}#{suffix}") + "?t=#{t}"
end
def source_disk_file_url(source)
disk_file_url(source.class, source.id)
def source_disk_file_url(source, suffix=nil)
disk_file_url(source.class, source.id, suffix)
end
def ctime(source_type, source_id)
return nil unless exist?(source_type, source_id)
def ctime(source_type, source_id, suffix)
return nil unless exist?(source_type, source_id, suffix)
File.ctime(disk_filename(source_type, source_id)).to_i
File.ctime(disk_filename(source_type, source_id, suffix)).to_i
end
def disk_auth_filename(source_type, source_id, type)

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

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

@ -0,0 +1,20 @@
class Admins::CreateLaboratoryService < ApplicationService
Error = Class.new(StandardError)
attr_reader :params
def initialize(params)
@params = params
end
def call
raise Error, '单位不能为空' if params[:school_id].blank?
raise Error, '该单位已存在' if Laboratory.exists?(school_id: params[:school_id])
ActiveRecord::Base.transaction do
laboratory = Laboratory.create!(school_id: params[:school_id])
laboratory.create_laboratory_setting!
end
end
end

@ -0,0 +1,51 @@
class Admins::SaveLaboratorySettingService < ApplicationService
attr_reader :laboratory, :laboratory_setting, :params
def initialize(laboratory, params)
@params = params
@laboratory = laboratory
@laboratory_setting = laboratory.laboratory_setting
end
def call
ActiveRecord::Base.transaction do
laboratory.identifier = strip params[:identifier]
laboratory_setting.name = strip params[:name]
laboratory_setting.navbar = navbar_config
laboratory_setting.footer = strip params[:footer]
laboratory.save!
laboratory_setting.save!
deal_logo_file
end
laboratory
end
private
def navbar_config
params[:navbar].map do |nav|
hash = {}
hash[:name] = strip nav[:name]
hash[:link] = strip nav[:link]
hash[:hidden] = nav[:hidden].to_s == 0
hash
end
end
def deal_logo_file
save_logo_file(params[:nav_logo], 'nav')
save_logo_file(params[:login_logo], 'login')
save_logo_file(params[:tab_logo], 'tab')
end
def save_logo_file(file, type)
return unless file.present? && file.is_a?(ActionDispatch::Http::UploadedFile)
file_path = Util::FileManage.source_disk_filename(laboratory_setting, type)
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
Util.write_file(file, file_path)
end
end

@ -1,3 +1,9 @@
class ApplicationService
include Callable
private
def strip(str)
str.to_s.strip.presence
end
end

@ -0,0 +1,19 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('云上实验室') %>
<% end %>
<div class="box search-form-container laboratory-list-form">
<%= form_tag(admins_laboratories_path(unsafe_params), method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-6 col-md-4 ml-3', placeholder: '学校名称/二级域名前缀检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<% end %>
<%= javascript_void_link '新建', class: 'btn btn-primary', data: { toggle: 'modal', target: '.admin-create-laboratory-modal' } %>
</div>
<div class="box laboratory-list-container">
<%= render(partial: 'admins/laboratories/shared/list', locals: { laboratories: @laboratories }) %>
</div>
<%= render 'admins/laboratories/shared/create_laboratory_modal' %>
<%= render 'admins/laboratories/shared/add_laboratory_user_modal' %>

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

@ -0,0 +1,30 @@
<div class="modal fade admin-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="admin-add-laboratory-user-form">
<%= hidden_field_tag(:laboratory_id, nil) %>
<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,28 @@
<div class="modal fade admin-create-laboratory-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="admin-create-laboratory-form" data-url="<%= admins_laboratories_path %>">
<div class="form-group d-flex">
<label for="school_id" class="col-form-label">选择单位:</label>
<div class="d-flex flex-column-reverse w-75">
<select id="school_id" name="school_id" class="form-control school-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,40 @@
<% school = laboratory.school %>
<td class="text-left"><%= school&.name || 'EduCoder主站' %></td>
<td class="text-left">
<% if laboratory.identifier %>
<%= link_to laboratory.site, "https://#{laboratory.site}", target: '_blank' %>
<% else %>
--
<% end %>
</td>
<td>
<% if school && school.identifier.present? %>
<%= link_to school.identifier.to_s, statistics_college_path(school.identifier), target: '_blank' %>
<% else %>
--
<% end %>
</td>
<td class="member-container">
<div class="laboratory-user">
<% laboratory.users.each do |user| %>
<span class="laboratory-user-item laboratory-user-item-<%= user.id %>">
<%= link_to user.real_name, "/users/#{user.login}", target: '_blank', data: { toggle: 'tooltip', title: '个人主页' } %>
<%= link_to(admins_laboratory_laboratory_user_path(laboratory, user_id: user.id),
method: :delete, remote: true, class: 'ml-1 delete-laboratory-user-action',
data: { confirm: '确认删除吗?' }) do %>
<i class="fa fa-close"></i>
<% end %>
</span>
<% end %>
</div>
</td>
<td><%= laboratory.created_at.strftime('%Y-%m-%d %H:%M') %></td>
<td class="action-container">
<%= link_to '定制', admins_laboratory_laboratory_setting_path(laboratory) %>
<% if school.present? && laboratory.id != 1 %>
<%= javascript_void_link '添加管理员', class: 'action', data: { laboratory_id: laboratory.id, toggle: 'modal', target: '.admin-add-laboratory-user-modal' } %>
<%= delete_link '删除', admins_laboratory_path(laboratory, element: ".laboratory-item-#{laboratory.id}"), class: 'delete-laboratory-action' %>
<% end %>
</td>

@ -0,0 +1,25 @@
<table class="table table-hover text-center laboratory-list-table">
<thead class="thead-light">
<tr>
<th width="20%" class="text-left">单位名称</th>
<th width="16%" class="text-left">域名</th>
<th width="10%">统计链接</th>
<th width="22%">管理员</th>
<th width="14%"><%= sort_tag('创建时间', name: 'id', path: admins_laboratories_path) %></th>
<th width="20%">操作</th>
</tr>
</thead>
<tbody>
<% if laboratories.present? %>
<% laboratories.each do |laboratory| %>
<tr class="laboratory-item laboratory-item-<%= laboratory.id %>">
<%= render 'admins/laboratories/shared/laboratory_item', laboratory: laboratory %>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'admins/shared/paginate', locals: { objects: laboratories } %>

@ -0,0 +1,131 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('云上实验室', admins_laboratories_path) %>
<% add_admin_breadcrumb('单位定制') %>
<% end %>
<div class="box edit-laboratory-setting-container">
<%= simple_form_for(@laboratory, url: admins_laboratory_laboratory_setting_path(@laboratory), method: 'patch', html: { enctype: 'multipart/form-data' }) do |f| %>
<% setting = @laboratory.laboratory_setting %>
<div class="form-group px-2 setting-item">
<div class="setting-item-head"><h6>网站域名设置</h6></div>
<div class="dropdown-divider"></div>
<div class="pl-0 py-3 setting-item-body">
<div class="input-group col-12 col-md-4 px-0">
<div class="input-group-prepend">
<span class="input-group-text">https://</span>
</div>
<%= text_field_tag :identifier, @laboratory.identifier,
maxlength: 15, class: 'form-control font-16',
'onKeyUp': 'value=value.replace(/[^\w\.\-\/]/ig,"").toLowerCase()',
style: 'text-transform:lowercase'%>
<div class="input-group-append">
<% rails_env = EduSetting.get('rails_env') %>
<span class="input-group-text font-14" id="site-prefix"><%= rails_env && rails_env != 'production' ? ".#{rails_env}.educoder.net" : '.educoder.net' %></span>
</div>
</div>
<%# if @laboratory.errors && @laboratory.errors.key?(:identifier) %>
<!-- <span id="identifier-error" class="danger text-danger">二级域名已被使用</span>-->
<%# end %>
</div>
</div>
<div class="form-group px-2 setting-item">
<div class="setting-item-head"><h6>网站名称设置</h6></div>
<div class="dropdown-divider"></div>
<div class="pl-0 py-3 setting-item-body">
<%= text_field_tag :name, setting.name, placeholder: '输入20个字以内的网站名称', maxlength: 20, class: 'form-control col-12 col-md-4' %>
</div>
</div>
<div class="form-group px-2 setting-item">
<div class="setting-item-head"><h6>Logo设置</h6></div>
<div class="dropdown-divider"></div>
<div class="pl-0 py-3 row setting-item-body">
<div class="col-12 col-md-4 logo-item">
<% nav_logo_img = setting.nav_logo_url %>
<div class="logo-item-left mr-3 <%= nav_logo_img ? 'has-img' : '' %>">
<img class="logo-item-img nav-logo-img" src="<%= nav_logo_img %>" style="<%= nav_logo_img.present? ? '' : 'display: none' %>"/>
<%= file_field_tag(:nav_logo, accept: 'image/png,image/jpg,image/jpeg', style: 'display: none', value: params[:nav_logo]) %>
<label for="nav_logo" class="logo-item-upload" data-toggle="tooltip" data-title="选择图片"></label>
</div>
<div class="logo-item-right">
<div class="logo-item-title flex-1">网站导航logo</div>
<div>格式PNG、JPG</div>
<div>尺寸高度38px以内宽等比例缩放</div>
</div>
</div>
<div class="col-12 col-md-4 logo-item">
<% login_logo_img = setting.login_logo_url %>
<div class="logo-item-left mr-3 <%= login_logo_img ? 'has-img' : '' %>">
<img class="logo-item-img login-logo-img" src="<%= login_logo_img %>" style="<%= login_logo_img.present? ? '' : 'display: none' %>"/>
<%= file_field_tag(:login_logo, accept: 'image/png,image/jpg,image/jpeg', style: 'display: none', value: params[:login_logo]) %>
<label for="login_logo" class="logo-item-upload" data-toggle="tooltip" data-title="选择图片"></label>
</div>
<div class="logo-item-right">
<div class="logo-item-title flex-1">登录页面logo</div>
<div>格式PNG、JPG</div>
<div>尺寸高度90px以内宽等比例缩放</div>
</div>
</div>
<div class="col-12 col-md-4 logo-item">
<% tab_logo_img = setting.tab_logo_url %>
<div class="logo-item-left mr-3 <%= tab_logo_img ? 'has-img' : '' %>">
<img class="logo-item-img tab-logo-img" src="<%= tab_logo_img %>" style="<%= tab_logo_img.present? ? '' : 'display: none' %>"/>
<%= file_field_tag(:tab_logo, accept: 'image/x-icon', style: 'display: none', value: params[:tab_logo]) %>
<label for="tab_logo" class="logo-item-upload" data-toggle="tooltip" data-title="选择图片"></label>
</div>
<div class="logo-item-right">
<div class="logo-item-title flex-1">浏览器导航栏logo</div>
<div>格式ico</div>
<div>尺寸16*16 32*32 48*48 64*64 </div>
</div>
</div>
</div>
</div>
<div class="form-group px-2 setting-item">
<div class="setting-item-head"><h6>导航设置</h6></div>
<div class="dropdown-divider"></div>
<div class="pl-0 py-3 setting-item-body">
<table class="table">
<thead class="thead-light">
<tr>
<th width="35%">导航名称</th>
<th width="50%">导航链接</th>
<th width="15%" class="text-center">是否展示</th>
</tr>
</thead>
<tbody>
<% (setting.navbar || setting.default_navbar).each do |nav| %>
<tr>
<td><%= text_field_tag('navbar[][name]', nav['name'], id: nil, class: 'form-control') %></td>
<td><%= text_field_tag('navbar[][link]', nav['link'], id: nil, class: 'form-control') %></td>
<td class="text-center">
<%= check_box_tag('navbar[][hidden]', 0, !nav['hidden'], id: nil, class: 'font-16') %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<div class="form-group px-2 setting-item">
<div class="setting-item-head"><h6>底部备案信息设置</h6></div>
<div class="dropdown-divider"></div>
<div class="pl-0 my-3 setting-item-body" id="laboratory-footer-editor">
<%= text_area_tag(:footer, setting.footer, placeholder: '请输入备案信息', class: 'form-control', style: 'display: none;') %>
</div>
</div>
<div class="error my-2 danger text-danger"></div>
<div class="form-group mt-4">
<%= javascript_void_link '保存', class: 'btn btn-primary mr-3 px-4 submit-btn' %>
<%= link_to '取消', admins_laboratories_path, class: 'btn btn-secondary px-4' %>
</div>
<% end %>
</div>

@ -0,0 +1,4 @@
$('.modal.admin-add-laboratory-user-modal').modal('hide');
$.notify({ message: '操作成功' });
$('.laboratory-list-table .laboratory-item-<%= current_laboratory.id %>').html("<%= j(render partial: 'admins/laboratories/shared/laboratory_item', locals: { laboratory: current_laboratory }) %>")

@ -0,0 +1,2 @@
$.notify({ message: '操作成功' });
$('.laboratory-list-container .laboratory-item-<%= current_laboratory.id %> .laboratory-user-item-<%= @laboratory_user.user_id %>').remove();

@ -33,23 +33,13 @@
<%= sidebar_item_group('#schools-submenu', '单位管理', icon: 'building') do %>
<li><%= sidebar_item(admins_schools_path, '单位列表', icon: 'university', controller: 'admins-schools') %></li>
<li><%= sidebar_item(admins_departments_path, '部门列表', icon: 'sitemap', controller: 'admins-departments') %></li>
<li><%= sidebar_item(admins_laboratories_path, '云上实验室', icon: 'cloud', controller: 'admins-laboratories') %></li>
<% end %>
</li>
<!-- <li>-->
<%#= sidebar_item_group('#course-submenu', '课堂+', icon: 'mortar-board') do %>
<!-- <li><%#= sidebar_item('#', '课程列表', icon: 'calendar', controller: '') %></li>-->
<!-- <li><%#= sidebar_item('#', '课堂列表', icon: 'book', controller: '') %></li>-->
<!-- <li><%#= sidebar_item('#', '实训作业', icon: 'file-code-o', controller: '') %></li>-->
<!-- <li><%#= sidebar_item('#', '项目列表', icon: 'sitemap', controller: '') %></li>-->
<%# end %>
<!-- </li>-->
<li>
<%= sidebar_item_group('#user-submenu', '用户', icon: 'user') do %>
<li><%= sidebar_item(admins_users_path, '用户列表', icon: 'user', controller: 'admins-users') %></li>
<!-- <li><%#= sidebar_item('#', '试用授权列表', icon: 'id-card-o', controller: '') %></li>-->
<!-- <li><%#= sidebar_item('#', '自动授权列表', icon: 'id-card', controller: '') %></li>-->
<% end %>
</li>

@ -0,0 +1,12 @@
json.setting do
setting = @laboratory.laboratory_setting
json.name setting.name || default_setting.name
json.nav_logo_url setting.nav_logo_url || default_setting.nav_logo_url
json.login_logo_url setting.login_logo_url || default_setting.login_logo_url
json.tab_logo_url setting.tab_logo_url || default_setting.tab_logo_url
json.navbar setting.navbar || default_setting.navbar
json.footer setting.footer || default_setting.footer
end

@ -1 +1,2 @@
admins-mirror_scripts: 'admins-mirror_repositories'
admins-mirror_scripts: 'admins-mirror_repositories'
admins-laboratory_settings: 'admins-laboratories'

@ -0,0 +1,7 @@
zh-CN:
activerecord:
models:
laboratory: ''
attributes:
laboratory:
identifier: '二级域名'

@ -824,6 +824,7 @@ Rails.application.routes.draw do
end
end
resource :template, only: [:show]
resource :setting, only: [:show]
end
namespace :admins do
@ -953,7 +954,7 @@ Rails.application.routes.draw do
resources :choose_mirror_repositories, only: [:new, :create]
resources :schools, only: [:index, :destroy]
resources :departments, only: [:index, :create, :edit, :update, :destroy] do
resource :department_member, only: [:create, :update, :destroy]
resource :department_member, only: [:create, :destroy]
post :merge, on: :collection
end
resources :myshixuns, only: [:index]
@ -971,6 +972,10 @@ Rails.application.routes.draw do
resources :carousels, only: [:index, :create, :update, :destroy] do
post :drag, on: :collection
end
resources :laboratories, only: [:index, :create, :destroy] do
resource :laboratory_setting, only: [:show, :update]
resource :laboratory_user, only: [:create, :destroy]
end
end
resources :colleges, only: [] do

@ -0,0 +1,12 @@
class CreateLaboratories < ActiveRecord::Migration[5.2]
def change
create_table :laboratories do |t|
t.references :school
t.string :identifier
t.timestamps
t.index :identifier, unique: true
end
end
end

@ -0,0 +1,8 @@
class CreateLaboratoryUsers < ActiveRecord::Migration[5.2]
def change
create_table :laboratory_users do |t|
t.references :laboratory
t.references :user
end
end
end

@ -0,0 +1,9 @@
class CreateLaboratorySettings < ActiveRecord::Migration[5.2]
def change
create_table :laboratory_settings do |t|
t.references :laboratory
t.text :config
end
end
end

@ -0,0 +1,22 @@
class InitEduCoderLaboratory < ActiveRecord::Migration[5.2]
def change
ActiveRecord::Base.transaction do
laboratory = Laboratory.create!(id: 1, identifier: 'www')
setting = laboratory.build_laboratory_setting
footer = %Q{
<p class="footer_con-p inline lineh-30 font-14">
<span class="font-18 fl">©</span>&nbsp;2019&nbsp;EduCoder
<a target="_blank" href="http://beian.miit.gov.cn/" class="ml15 mr15" style="color: rgb(136, 136, 136);">ICP17009477</a>
<a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=43019002000962" class="mr15" style="color: rgb(136, 136, 136);">
<img class="vertical4" src="https://ali-cdn.educoder.net/react/build/static/media/beian.d0289dc0.png">43019002000962
</a>
<a href="https://team.trustie.net" target="_blank" style="color: rgb(136, 136, 136);">Trustie</a>
&nbsp;&nbsp;&nbsp;&amp;&nbsp;&nbsp;&nbsp;IntelliDE inside.
<span class="mr15"> </span></p>
}
config = setting.class.default_config.merge(name: 'EduCoder', footer: footer)
setting.config = config
setting.save!
end
end
end

File diff suppressed because one or more lines are too long

@ -25274,6 +25274,114 @@ input.form-control {
color: #6c757d;
}
/* line 4, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratories-index-page .laboratory-list-table .member-container .laboratory-user {
display: -webkit-box;
display: flex;
-webkit-box-pack: center;
justify-content: center;
flex-wrap: wrap;
}
/* line 9, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratories-index-page .laboratory-list-table .member-container .laboratory-user .laboratory-user-item {
display: -webkit-box;
display: flex;
-webkit-box-align: center;
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;
}
/* line 27, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item {
display: -webkit-box;
display: flex;
}
/* line 30, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-img, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-img {
display: block;
width: 80px;
height: 80px;
}
/* line 36, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload {
cursor: pointer;
position: absolute;
top: 0;
width: 80px;
height: 80px;
background: #F5F5F5;
border: 1px solid #E5E5E5;
}
/* line 45, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload::before, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload::before {
content: '';
position: absolute;
top: 27px;
left: 39px;
width: 2px;
height: 26px;
background: #E5E5E5;
}
/* line 55, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload::after, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload::after {
content: '';
position: absolute;
top: 39px;
left: 27px;
width: 26px;
height: 2px;
background: #E5E5E5;
}
/* line 66, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left {
position: relative;
width: 80px;
height: 80px;
}
/* line 72, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left.has-img .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left.has-img .logo-item-upload {
display: none;
}
/* line 77, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left.has-img:hover .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left.has-img:hover .logo-item-upload {
display: block;
background: rgba(145, 145, 145, 0.8);
}
/* line 85, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-right, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-right {
display: -webkit-box;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
-webkit-box-pack: justify;
justify-content: space-between;
color: #777777;
font-size: 12px;
}
/* line 93, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-title, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-title {
color: #23272B;
font-size: 14px;
}
/* line 4, app/assets/stylesheets/admins/library_applies.scss */
.admins-library-applies-index-page .library-applies-list-container span.apply-status-agreed {
color: #28a745;

@ -134528,6 +134528,256 @@ $(document).on('turbolinks:load', function() {
}
})
;
$(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);
}
});
})
}
});
$(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');
}
});
}
});
$(document).on('turbolinks:load', function() {
if ($('body.admins-library-applies-index-page').length > 0) {
var $searchFrom = $('.library-applies-list-form');

@ -25274,6 +25274,114 @@ input.form-control {
color: #6c757d;
}
/* line 4, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratories-index-page .laboratory-list-table .member-container .laboratory-user {
display: -webkit-box;
display: flex;
-webkit-box-pack: center;
justify-content: center;
flex-wrap: wrap;
}
/* line 9, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratories-index-page .laboratory-list-table .member-container .laboratory-user .laboratory-user-item {
display: -webkit-box;
display: flex;
-webkit-box-align: center;
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;
}
/* line 27, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item {
display: -webkit-box;
display: flex;
}
/* line 30, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-img, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-img {
display: block;
width: 80px;
height: 80px;
}
/* line 36, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload {
cursor: pointer;
position: absolute;
top: 0;
width: 80px;
height: 80px;
background: #F5F5F5;
border: 1px solid #E5E5E5;
}
/* line 45, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload::before, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload::before {
content: '';
position: absolute;
top: 27px;
left: 39px;
width: 2px;
height: 26px;
background: #E5E5E5;
}
/* line 55, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload::after, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload::after {
content: '';
position: absolute;
top: 39px;
left: 27px;
width: 26px;
height: 2px;
background: #E5E5E5;
}
/* line 66, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left {
position: relative;
width: 80px;
height: 80px;
}
/* line 72, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left.has-img .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left.has-img .logo-item-upload {
display: none;
}
/* line 77, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left.has-img:hover .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left.has-img:hover .logo-item-upload {
display: block;
background: rgba(145, 145, 145, 0.8);
}
/* line 85, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-right, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-right {
display: -webkit-box;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
-webkit-box-pack: justify;
justify-content: space-between;
color: #777777;
font-size: 12px;
}
/* line 93, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-title, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-title {
color: #23272B;
font-size: 14px;
}
/* line 4, app/assets/stylesheets/admins/library_applies.scss */
.admins-library-applies-index-page .library-applies-list-container span.apply-status-agreed {
color: #28a745;
@ -26246,6 +26354,113 @@ input.form-control {
.admins-identity-authentications-index-page .identity-authentication-list-container span.apply-status-3 {
color: #6c757d;
}
/* line 4, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratories-index-page .laboratory-list-table .member-container .laboratory-user {
display: -webkit-box;
display: flex;
-webkit-box-pack: center;
justify-content: center;
flex-wrap: wrap;
}
/* line 9, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratories-index-page .laboratory-list-table .member-container .laboratory-user .laboratory-user-item {
display: -webkit-box;
display: flex;
-webkit-box-align: center;
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;
}
/* line 27, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item {
display: -webkit-box;
display: flex;
}
/* line 30, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-img, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-img {
display: block;
width: 80px;
height: 80px;
}
/* line 36, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload {
cursor: pointer;
position: absolute;
top: 0;
width: 80px;
height: 80px;
background: #F5F5F5;
border: 1px solid #E5E5E5;
}
/* line 45, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload::before, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload::before {
content: '';
position: absolute;
top: 27px;
left: 39px;
width: 2px;
height: 26px;
background: #E5E5E5;
}
/* line 55, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-upload::after, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-upload::after {
content: '';
position: absolute;
top: 39px;
left: 27px;
width: 26px;
height: 2px;
background: #E5E5E5;
}
/* line 66, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left {
position: relative;
width: 80px;
height: 80px;
}
/* line 72, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left.has-img .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left.has-img .logo-item-upload {
display: none;
}
/* line 77, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-left.has-img:hover .logo-item-upload, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-left.has-img:hover .logo-item-upload {
display: block;
background: rgba(145, 145, 145, 0.8);
}
/* line 85, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-right, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-right {
display: -webkit-box;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
flex-direction: column;
-webkit-box-pack: justify;
justify-content: space-between;
color: #777777;
font-size: 12px;
}
/* line 93, app/assets/stylesheets/admins/laboratories.scss */
.admins-laboratory-settings-show-page .edit-laboratory-setting-container .logo-item-title, .admins-laboratory-settings-update-page .edit-laboratory-setting-container .logo-item-title {
color: #23272B;
font-size: 14px;
}
/* line 4, app/assets/stylesheets/admins/library_applies.scss */
.admins-library-applies-index-page .library-applies-list-container span.apply-status-agreed {
color: #28a745;
Loading…
Cancel
Save