diff --git a/Gemfile b/Gemfile
index ac2778c59..7a37b7c44 100644
--- a/Gemfile
+++ b/Gemfile
@@ -98,3 +98,7 @@ gem 'aasm'
gem 'enumerize'
gem 'diffy'
+
+# oauth2
+gem 'omniauth', '~> 1.9.0'
+gem 'omniauth-oauth2', '~> 1.6.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index aabf3ffba..4b7902353 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -121,7 +121,7 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
- hashie (3.6.0)
+ hashie (3.5.7)
htmlentities (4.3.4)
httparty (0.16.2)
multi_xml (>= 0.5.2)
@@ -179,6 +179,12 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
+ omniauth (1.9.0)
+ hashie (>= 3.4.6, < 3.7.0)
+ rack (>= 1.6.2, < 3)
+ omniauth-oauth2 (1.6.0)
+ oauth2 (~> 1.1)
+ omniauth (~> 1.9)
pdfkit (0.8.4.1)
popper_js (1.14.5)
public_suffix (4.0.1)
@@ -381,6 +387,8 @@ DEPENDENCIES
listen (>= 3.0.5, < 3.2)
mysql2 (>= 0.4.4, < 0.6.0)
oauth2
+ omniauth (~> 1.9.0)
+ omniauth-oauth2 (~> 1.6.0)
pdfkit
puma (~> 3.11)
rack-cors
diff --git a/app/assets/javascripts/admins/laboratories/edit.js b/app/assets/javascripts/admins/laboratories/edit.js
new file mode 100644
index 000000000..63b26bbe0
--- /dev/null
+++ b/app/assets/javascripts/admins/laboratories/edit.js
@@ -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);
+ }
+ });
+ })
+ }
+});
\ No newline at end of file
diff --git a/app/assets/javascripts/admins/laboratories/index.js b/app/assets/javascripts/admins/laboratories/index.js
new file mode 100644
index 000000000..abb7cb72d
--- /dev/null
+++ b/app/assets/javascripts/admins/laboratories/index.js
@@ -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');
+ }
+ });
+ }
+});
\ No newline at end of file
diff --git a/app/assets/stylesheets/admins/laboratories.scss b/app/assets/stylesheets/admins/laboratories.scss
new file mode 100644
index 000000000..ad5c8c5a8
--- /dev/null
+++ b/app/assets/stylesheets/admins/laboratories.scss
@@ -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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 1d6f89ec0..fed6ec280 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -183,20 +183,6 @@ class AccountsController < ApplicationController
end
private
- def autologin_cookie_name
- edu_setting('autologin_cookie_name') || 'autologin'
- end
-
- def logout_user
- if User.current.logged?
- if autologin = cookies.delete(autologin_cookie_name)
- User.current.delete_autologin_token(autologin)
- end
- User.current.delete_session_token(session[:tk])
- self.logged_user = nil
- end
- session[:user_id] = nil
- end
# type 事件类型 1:用户注册 2:忘记密码 3: 绑定手机 4: 绑定邮箱, 5: 验证手机号是否有效 # 如果有新的继续后面加
# login_type 1:手机类型 2:邮箱类型
diff --git a/app/controllers/admins/laboratories_controller.rb b/app/controllers/admins/laboratories_controller.rb
new file mode 100644
index 000000000..e393c6677
--- /dev/null
+++ b/app/controllers/admins/laboratories_controller.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/admins/laboratory_settings_controller.rb b/app/controllers/admins/laboratory_settings_controller.rb
new file mode 100644
index 000000000..f9676bfd3
--- /dev/null
+++ b/app/controllers/admins/laboratory_settings_controller.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/admins/laboratory_users_controller.rb b/app/controllers/admins/laboratory_users_controller.rb
new file mode 100644
index 000000000..36e389a3e
--- /dev/null
+++ b/app/controllers/admins/laboratory_users_controller.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index b539a0c68..5c93b08b9 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -7,6 +7,8 @@ class ApplicationController < ActionController::Base
include ControllerRescueHandler
include GitHelper
include LoggerHelper
+ include LaboratoryHelper
+ include LoginHelper
protect_from_forgery prepend: true, unless: -> { request.format.json? }
@@ -234,12 +236,6 @@ class ApplicationController < ActionController::Base
# end
end
- def start_user_session(user)
- session[:user_id] = user.id
- session[:ctime] = Time.now.utc.to_i
- session[:atime] = Time.now.utc.to_i
- end
-
def user_setup
# reacct静态资源加载不需要走这一步
return if params[:controller] == "main"
@@ -280,17 +276,6 @@ class ApplicationController < ActionController::Base
# User.current = User.find 81403
end
- # Sets the logged in user
- def logged_user=(user)
- reset_session
- if user && user.is_a?(User)
- User.current = user
- start_user_session(user)
- else
- User.current = User.anonymous
- end
- end
-
# Returns the current user or nil if no user is logged in
# and starts a session if needed
def find_current_user
@@ -306,10 +291,6 @@ class ApplicationController < ActionController::Base
end
end
- def autologin_cookie_name
- edu_setting('autologin_cookie_name').presence || 'autologin'
- end
-
def try_to_autologin
if cookies[autologin_cookie_name]
# auto-login feature starts a new session
@@ -620,22 +601,6 @@ class ApplicationController < ActionController::Base
cookies[:fileDownload] = true
end
- def set_autologin_cookie(user)
- token = Token.get_or_create_permanent_login_token(user, "autologin")
- cookie_options = {
- :value => token.value,
- :expires => 1.month.from_now,
- :path => '/',
- :secure => false,
- :httponly => true
- }
- if edu_setting('cookie_domain').present?
- cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain'))
- end
- cookies[autologin_cookie_name] = cookie_options
- logger.info("cookies is #{cookies}")
- end
-
# 149课程的评审用户数据创建(包含创建课堂学生)
def open_class_user
user = User.find_by(login: "OpenClassUser")
diff --git a/app/controllers/bind_users_controller.rb b/app/controllers/bind_users_controller.rb
new file mode 100644
index 000000000..354b2993b
--- /dev/null
+++ b/app/controllers/bind_users_controller.rb
@@ -0,0 +1,22 @@
+class BindUsersController < ApplicationController
+ before_action :require_login
+
+ def create
+ user = CreateBindUserService.call(current_user, create_params)
+ successful_authentication(user) if user.id != current_user.id
+
+ render_ok
+ rescue ApplicationService::Error => ex
+ render_error(ex.message)
+ end
+
+ def new_user
+ current_user
+ end
+
+ private
+
+ def create_params
+ params.permit(:username, :password, :type, :not_bind)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/concerns/laboratory_helper.rb b/app/controllers/concerns/laboratory_helper.rb
new file mode 100644
index 000000000..fbb18b36d
--- /dev/null
+++ b/app/controllers/concerns/laboratory_helper.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/concerns/login_helper.rb b/app/controllers/concerns/login_helper.rb
new file mode 100644
index 000000000..e94cf8a21
--- /dev/null
+++ b/app/controllers/concerns/login_helper.rb
@@ -0,0 +1,69 @@
+module LoginHelper
+ extend ActiveSupport::Concern
+
+ def edu_setting(name)
+ EduSetting.get(name)
+ end
+
+ def autologin_cookie_name
+ edu_setting('autologin_cookie_name').presence || 'autologin'
+ end
+
+ def set_autologin_cookie(user)
+ token = Token.get_or_create_permanent_login_token(user, "autologin")
+ cookie_options = {
+ :value => token.value,
+ :expires => 1.month.from_now,
+ :path => '/',
+ :secure => false,
+ :httponly => true
+ }
+ if edu_setting('cookie_domain').present?
+ cookie_options = cookie_options.merge(domain: edu_setting('cookie_domain'))
+ end
+ cookies[autologin_cookie_name] = cookie_options
+ Rails.logger.info("cookies is #{cookies}")
+ end
+
+ def successful_authentication(user)
+ Rails.logger.info("id: #{user&.id} Successful authentication start: '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}")
+ # Valid user
+ self.logged_user = user
+
+ # generate a key and set cookie if autologin
+ set_autologin_cookie(user)
+
+ UserAction.create(action_id: user&.id, action_type: 'Login', user_id: user&.id, ip: request.remote_ip)
+ user.update_column(:last_login_on, Time.now)
+ # 注册完成后有一天的试用申请(先去掉)
+ # UserDayCertification.create(user_id: user.id, status: 1)
+ end
+
+ def logout_user
+ if User.current.logged?
+ if autologin = cookies.delete(autologin_cookie_name)
+ User.current.delete_autologin_token(autologin)
+ end
+ User.current.delete_session_token(session[:tk])
+ self.logged_user = nil
+ end
+ session[:user_id] = nil
+ end
+
+ # Sets the logged in user
+ def logged_user=(user)
+ reset_session
+ if user && user.is_a?(User)
+ User.current = user
+ start_user_session(user)
+ else
+ User.current = User.anonymous
+ end
+ end
+
+ def start_user_session(user)
+ session[:user_id] = user.id
+ session[:ctime] = Time.now.utc.to_i
+ session[:atime] = Time.now.utc.to_i
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb
index a3b20c598..0b3e35519 100644
--- a/app/controllers/courses_controller.rb
+++ b/app/controllers/courses_controller.rb
@@ -35,7 +35,7 @@ class CoursesController < ApplicationController
:transfer_to_course_group, :delete_from_course, :export_member_scores_excel,
:search_users, :add_students_by_search, :get_historical_courses, :add_teacher_popup,
:add_teacher, :export_couser_info, :export_member_act_score,
- :update_informs, :new_informs, :delete_informs]
+ :update_informs, :new_informs, :delete_informs, :switch_to_student]
before_action :admin_allowed, only: [:set_invite_code_halt, :set_public_or_private, :change_course_admin,
:set_course_group, :create_group_by_importing_file,
:update_task_position, :tasks_list]
@@ -552,7 +552,7 @@ class CoursesController < ApplicationController
def change_member_role
tip_exception("请至少选择一个角色") if params[:roles].blank?
tip_exception("不能具有老师、助教两种角色") if params[:roles].include?("PROFESSOR") && params[:roles].include?("ASSISTANT_PROFESSOR")
- tip_exception("管理员不能切换为助教或老师") if @user_course_identity == Course::CREATOR &&
+ tip_exception("管理员不能切换为助教或老师") if params[:user_id].to_i == @course.tea_id &&
(params[:roles].include?("PROFESSOR") || params[:roles].include?("ASSISTANT_PROFESSOR"))
course_members = @course.course_members.where(user_id: params[:user_id])
@@ -681,13 +681,19 @@ class CoursesController < ApplicationController
course_member = @course.course_members.find_by!(user_id: current_user.id, is_active: 1)
tip_exception("切换失败") if course_member.STUDENT?
- course_student = CourseMember.find_by!(user_id: current_user.id, role: %i[STUDENT], course_id: @course.id)
- course_member.update_attributes(is_active: 0)
- course_student.update_attributes(is_active: 1)
+ course_student = CourseMember.find_by(user_id: current_user.id, role: %i[STUDENT], course_id: @course.id)
+ course_member.update_attributes!(is_active: 0)
+ if course_student
+ course_student.update_attributes!(is_active: 1)
+ else
+ # 学生身份不存在则创建
+ CourseMember.create!(user_id: current_user.id, role: 4, course_id: @course.id)
+ CourseAddStudentCreateWorksJob.perform_later(@course.id, [current_user.id])
+ end
normal_status(0, "切换成功")
rescue => e
uid_logger_error("switch_to_student error: #{e.message}")
- tip_exception("切换失败")
+ tip_exception(e.message)
raise ActiveRecord::Rollback
end
end
@@ -1127,7 +1133,7 @@ class CoursesController < ApplicationController
def top_banner
@user = current_user
- @is_teacher = @user_course_identity < Course::STUDENT
+ @switch_student = Course::BUSINESS < @user_course_identity && @user_course_identity < Course::STUDENT
@is_student = @user_course_identity == Course::STUDENT
@course.increment!(:visits)
end
diff --git a/app/controllers/exercise_questions_controller.rb b/app/controllers/exercise_questions_controller.rb
index 9eeba6adc..aacef6bc7 100644
--- a/app/controllers/exercise_questions_controller.rb
+++ b/app/controllers/exercise_questions_controller.rb
@@ -619,7 +619,7 @@ class ExerciseQuestionsController < ApplicationController
:status => 0
}
ExerciseShixunAnswer.create(ex_shixun_option)
- new_obj_score = @c_score
+ new_obj_score = ex_obj_score + @c_score
end
total_scores = new_obj_score + ex_subj_score
if total_scores < 0.0
diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb
index bc0da8ffc..9979ae48f 100644
--- a/app/controllers/exercises_controller.rb
+++ b/app/controllers/exercises_controller.rb
@@ -694,6 +694,7 @@ class ExercisesController < ApplicationController
#首页批量或单独 立即发布,应是跳出弹窗,设置开始时间和截止时间。
def publish
+ group_ids = params[:group_ids]&.reject(&:blank?)
if params[:detail].blank?
tip_exception("缺少截止时间参数") if params[:end_time].blank?
tip_exception("截止时间不能早于当前时间") if params[:end_time] <= strf_time(Time.now)
@@ -701,7 +702,6 @@ class ExercisesController < ApplicationController
@course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day)
else
group_end_times = params[:group_end_times].reject(&:blank?).map{|time| time.to_time}
- group_ids = params[:group_ids].reject(&:blank?)
tip_exception("缺少截止时间参数") if group_end_times.blank?
tip_exception("截止时间和分班参数的个数不一致") if group_end_times.length != group_ids.length
group_end_times.each do |time|
@@ -978,7 +978,8 @@ class ExercisesController < ApplicationController
:status => nil,
:commit_status => 0,
:objective_score => 0.0,
- :subjective_score => -1.0
+ :subjective_score => -1.0,
+ :commit_method => 0
}
redo_exercise_users = @exercise_users.exercise_commit_users(user_ids)
redo_exercise_users.update_all(redo_option)
@@ -1102,10 +1103,11 @@ class ExercisesController < ApplicationController
ActiveRecord::Base.transaction do
begin
can_commit_exercise = false
- Rails.logger.info("######____________params[:commit_method]_________################{params[:commit_method]}")
- if (@user_course_identity > Course::ASSISTANT_PROFESSOR) && params[:commit_method].present? #为学生时
+ user_left_time = nil
+ if @user_course_identity > Course::ASSISTANT_PROFESSOR #为学生时
if params[:commit_method].to_i == 2 #自动提交时
user_left_time = get_exercise_left_time(@exercise,current_user)
+ Rails.logger.info("######__________auto_commit_user_left_time_________################{user_left_time}")
if user_left_time.to_i <= 0
can_commit_exercise = true
end
@@ -1130,10 +1132,10 @@ class ExercisesController < ApplicationController
CommitExercsieNotifyJobJob.perform_later(@exercise.id, current_user.id)
normal_status(0,"试卷提交成功!")
else
- normal_status(-1,"提交失败,请重试!")
+ normal_status(-2,"#{user_left_time.to_i}")
end
else
- normal_status(-1,"提交失败,请重试!")
+ normal_status(-1,"提交失败,当前用户不为课堂学生!")
end
rescue Exception => e
uid_logger_error(e.message)
@@ -1150,7 +1152,7 @@ class ExercisesController < ApplicationController
# 1 老师权限,0 学生权限
@is_teacher_or = (@user_course_identity < Course::STUDENT) ? 1 : 0
@student_status = 2
- @exercise_questions = @exercise.exercise_questions.includes(:exercise_shixun_challenges,:exercise_standard_answers,:exercise_answers,:exercise_shixun_answers).order("question_number ASC")
+ @exercise_questions = @exercise.exercise_questions.includes(:exercise_shixun_challenges,:exercise_standard_answers,:exercise_answers,:exercise_shixun_answers,:exercise_answer_comments).order("question_number ASC")
@question_status = []
get_exercise_status = @exercise.get_exercise_status(current_user) #当前用户的试卷状态
@ex_answer_status = @exercise.get_exercise_status(@ex_user&.user) #当前试卷用户的试卷状态
@@ -1321,7 +1323,7 @@ class ExercisesController < ApplicationController
end
rescue Exception => e
uid_logger_error(e.message)
- tip_exception("页面调用失败!")
+ tip_exception(e.message)
raise ActiveRecord::Rollback
end
end
@@ -1707,9 +1709,9 @@ class ExercisesController < ApplicationController
ques_number = q.question_number
end
if q.question_type != Exercise::PRACTICAL
- ques_vote = q.exercise_answers.search_exercise_answer("user_id",user_id)
+ ques_vote = q.exercise_answers.select{|answer| answer.user_id == user_id}
else
- ques_vote = q.exercise_shixun_answers.search_shixun_answers("user_id",user_id)
+ ques_vote = q.exercise_shixun_answers.select{|answer| answer.user_id == user_id}
end
ques_status = 0
if ques_vote.present?
diff --git a/app/controllers/homework_commons_controller.rb b/app/controllers/homework_commons_controller.rb
index 9941e1d42..60cf2d6c5 100644
--- a/app/controllers/homework_commons_controller.rb
+++ b/app/controllers/homework_commons_controller.rb
@@ -159,13 +159,20 @@ class HomeworkCommonsController < ApplicationController
end
# 作品状态 0: 未提交, 1 按时提交, 2 延迟提交
- unless params[:work_status].blank?
- @student_works = @student_works.where(work_status: params[:work_status])
+ if params[:work_status].present?
+ params_work_status = request.get? ? params[:work_status].split(",") : params[:work_status]
+ work_status = params_work_status.map{|status| status.to_i}
+ all_student_works = @student_works.left_joins(:myshixun)
+ @student_works = all_student_works.where(work_status: work_status)
+
+ @student_works = @student_works.or(all_student_works.where(work_status: 0)).or(all_student_works.where(myshixuns: {status: 0})) if work_status.include?(3)
+ @student_works = @student_works.or(all_student_works.where(myshixuns: {status: 1})) if work_status.include?(4)
end
# 分班情况
unless params[:course_group].blank?
- group_user_ids = @course.students.where(course_group_id: params[:course_group]).pluck(:user_id)
+ group_ids = request.get? ? params[:course_group].split(",") : params[:course_group]
+ group_user_ids = @course.students.where(course_group_id: group_ids).pluck(:user_id)
# 有分组只可能是老师身份查看列表
@student_works = @student_works.where(user_id: group_user_ids)
end
@@ -477,7 +484,7 @@ class HomeworkCommonsController < ApplicationController
publish_time = setting[:publish_time] == "" ? Time.now : setting[:publish_time]
# 截止时间为空时取发布时间后一个月
- end_time = setting[:end_time] == "" ? Time.at(publish_time.to_time.to_i+30*24*3600) : setting[:end_time]
+ end_time = setting[:end_time]
HomeworkGroupSetting.where(homework_common_id: @homework.id, course_group_id: setting[:group_id]).
update_all(publish_time: publish_time, end_time: end_time)
setting_group_ids << setting[:group_id]
@@ -1044,6 +1051,7 @@ class HomeworkCommonsController < ApplicationController
def publish_homework
tip_exception("请至少选择一个分班") if params[:group_ids].blank? && @course.course_groups.size != 0
+ group_ids = params[:group_ids]&.reject(&:blank?)
if params[:detail].blank?
tip_exception("缺少截止时间参数") if params[:end_time].blank?
tip_exception("截止时间不能早于当前时间") if params[:end_time] <= strf_time(Time.now)
@@ -1051,7 +1059,6 @@ class HomeworkCommonsController < ApplicationController
@course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day)
else
group_end_times = params[:group_end_times].reject(&:blank?).map{|time| time.to_time}
- group_ids = params[:group_ids].reject(&:blank?)
tip_exception("缺少截止时间参数") if group_end_times.blank?
tip_exception("截止时间和分班参数的个数不一致") if group_end_times.length != group_ids.length
group_end_times.each do |time|
@@ -1165,7 +1172,7 @@ class HomeworkCommonsController < ApplicationController
# 可立即截止的分班:统一设置则是用户管理的所有分班,否则是当前用户管理的分班中已发布且未截止的
charge_group_ids = @course.charge_group_ids(@current_user) # 当前用户管理的分班
group_ids = @homework.unified_setting ? charge_group_ids :
- @homework.homework_group_settings.where(course_group_id: charge_group_ids).none_end.pluck(:course_group_id)
+ @homework.homework_group_settings.where(course_group_id: charge_group_ids).published_no_end.pluck(:course_group_id)
@course_groups = @course.course_groups.where(id: group_ids)
else
tip_exception("没有可截止的分班")
diff --git a/app/controllers/oauth/base_controller.rb b/app/controllers/oauth/base_controller.rb
new file mode 100644
index 000000000..e2eb26a2a
--- /dev/null
+++ b/app/controllers/oauth/base_controller.rb
@@ -0,0 +1,20 @@
+class Oauth::BaseController < ActionController::Base
+ include RenderHelper
+ include LoginHelper
+
+ skip_before_action :verify_authenticity_token
+
+ private
+
+ def session_user_id
+ session[:user_id]
+ end
+
+ def current_user
+ @_current_user ||= User.find_by(id: session_user_id)
+ end
+
+ def auth_hash
+ request.env['omniauth.auth']
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/oauth/qq_controller.rb b/app/controllers/oauth/qq_controller.rb
new file mode 100644
index 000000000..4b9a46443
--- /dev/null
+++ b/app/controllers/oauth/qq_controller.rb
@@ -0,0 +1,9 @@
+class Oauth::QQController < Oauth::BaseController
+ def create
+ user, new_user = Oauth::CreateOrFindQqAccountService.call(current_user, auth_hash)
+
+ successful_authentication(user)
+
+ render_ok(new_user: new_user)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/oauth/wechat_controller.rb b/app/controllers/oauth/wechat_controller.rb
new file mode 100644
index 000000000..6c0c53eb6
--- /dev/null
+++ b/app/controllers/oauth/wechat_controller.rb
@@ -0,0 +1,11 @@
+class Oauth::WechatController < Oauth::BaseController
+ def create
+ user, new_user = Oauth::CreateOrFindWechatAccountService.call(current_user ,params)
+
+ successful_authentication(user)
+
+ render_ok(new_user: new_user)
+ rescue Oauth::CreateOrFindWechatAccountService::Error => ex
+ render_error(ex.message)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb
index 2259907e4..da5917e1b 100644
--- a/app/controllers/polls_controller.rb
+++ b/app/controllers/polls_controller.rb
@@ -254,6 +254,7 @@ class PollsController < ApplicationController
#首页批量或单独 立即发布,应是跳出弹窗,设置开始时间和截止时间。
def publish
+ group_ids = params[:group_ids]&.reject(&:blank?)
if params[:detail].blank?
tip_exception("缺少截止时间参数") if params[:end_time].blank?
tip_exception("截止时间不能早于当前时间") if params[:end_time] <= strf_time(Time.now)
@@ -261,7 +262,6 @@ class PollsController < ApplicationController
@course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day)
else
group_end_times = params[:group_end_times].reject(&:blank?).map{|time| time.to_time}
- group_ids = params[:group_ids].reject(&:blank?)
tip_exception("缺少截止时间参数") if group_end_times.blank?
tip_exception("截止时间和分班参数的个数不一致") if group_end_times.length != group_ids.length
group_end_times.each do |time|
diff --git a/app/controllers/question_banks_controller.rb b/app/controllers/question_banks_controller.rb
index 60b9a807c..ddb0f3ce1 100644
--- a/app/controllers/question_banks_controller.rb
+++ b/app/controllers/question_banks_controller.rb
@@ -90,23 +90,45 @@ class QuestionBanksController < ApplicationController
def send_to_course
banks = @object_type.classify.constantize.where(id: params[:object_id])
course = current_user.manage_courses.find_by!(id: params[:course_id])
+ task_ids = []
+ homework_type = ""
+ container_type = ""
banks.each do |bank|
case @object_type
when 'HomeworkBank' # 作业
- quote_homework_bank bank, course
+ task = quote_homework_bank bank, course
+ homework_type = task.homework_type
when 'ExerciseBank'
- if bank.container_type == 'Exercise' # 试卷
- quote_exercise_bank bank, course
+ container_type = bank.container_type
+ if container_type == 'Exercise' # 试卷
+ task = quote_exercise_bank bank, course
else # 问卷
- quote_poll_bank bank, course
+ task = quote_poll_bank bank, course
end
when 'GtaskBank'
- quote_gtask_bank bank, course
+ task = quote_gtask_bank bank, course
when 'GtopicBank'
- quote_gtopic_bank bank, course
+ task = quote_gtopic_bank bank, course
end
+ task_ids << task.id if task
end
- normal_status("发送成功")
+
+ case @object_type
+ when 'HomeworkBank' # 作业
+ category_id = course.course_modules.find_by(module_type: homework_type == "normal" ? "common_homework" : "group_homework")&.id
+ when 'ExerciseBank'
+ if container_type == 'Exercise' # 试卷
+ category_id = course.course_modules.find_by(module_type: "exercise")&.id
+ else # 问卷
+ category_id = course.course_modules.find_by(module_type: "poll")&.id
+ end
+ when 'GtaskBank'
+ category_id = course.course_modules.find_by(module_type: "graduation")&.id
+ when 'GtopicBank'
+ category_id = course.course_modules.find_by(module_type: "graduation")&.id
+ end
+
+ render :json => {task_ids: task_ids, category_id: category_id, status: 0, message: "发送成功"}
end
def destroy
diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb
new file mode 100644
index 000000000..ce5481147
--- /dev/null
+++ b/app/controllers/settings_controller.rb
@@ -0,0 +1,5 @@
+class SettingsController < ApplicationController
+ def show
+ @laboratory = current_laboratory
+ end
+end
\ No newline at end of file
diff --git a/app/forms/users/update_password_form.rb b/app/forms/users/update_password_form.rb
index 023caa40f..4da341839 100644
--- a/app/forms/users/update_password_form.rb
+++ b/app/forms/users/update_password_form.rb
@@ -4,5 +4,4 @@ class Users::UpdatePasswordForm
attr_accessor :password, :old_password
validates :password, presence: true
- validates :old_password, presence: true
end
\ No newline at end of file
diff --git a/app/helpers/courses_helper.rb b/app/helpers/courses_helper.rb
index fb7bd1a88..9afbdd3af 100644
--- a/app/helpers/courses_helper.rb
+++ b/app/helpers/courses_helper.rb
@@ -62,7 +62,7 @@ module CoursesHelper
course_board = course.course_board
"/courses/#{course.id}/boards/#{course_board.id}"
when "course_group"
- "/courses/#{course.id}/students"
+ "/courses/#{course.id}/course_groups"
end
end
diff --git a/app/helpers/exercises_helper.rb b/app/helpers/exercises_helper.rb
index ef9261990..e13f754be 100644
--- a/app/helpers/exercises_helper.rb
+++ b/app/helpers/exercises_helper.rb
@@ -10,9 +10,9 @@ module ExercisesHelper
exercise_obj_status.each do |q|
q_type = q.question_type
if q_type == Exercise::PRACTICAL
- answers_content = q.exercise_shixun_answers.search_shixun_answers("user_id",user_id)
+ answers_content = q.exercise_shixun_answers.select{|answer| answer.user_id == user_id}
else
- answers_content = q.exercise_answers.search_answer_users("user_id",user_id)
+ answers_content = q.exercise_answers.select{|answer| answer.user_id == user_id}
end
if q_type <= Exercise::JUDGMENT
@@ -40,7 +40,7 @@ module ExercisesHelper
ques_score = 0.0
end
else
- ques_score = answers_content.score_reviewed.select(:score).pluck(:score).sum
+ ques_score = answers_content.select{|answer| answer.score >= 0.0}.pluck(:score).sum
end
if ques_score >= q.question_score #满分作答为正确
@@ -64,7 +64,7 @@ module ExercisesHelper
exercise_sub_status = exercise_questions.find_by_custom("question_type",Exercise::SUBJECTIVE) #主观题
@ex_sub_array = [] #主观题的已答/未答
exercise_sub_status.each do |s|
- sub_answer = s.exercise_answers.search_answer_users("user_id",user_id) #主观题只有一个回答
+ sub_answer = s.exercise_answers.select{|answer| answer.user_id == user_id} #主观题只有一个回答
if sub_answer.present? && sub_answer.first.score >= 0.0
if s.question_score <= sub_answer.first.score
stand_status = 1
@@ -772,12 +772,12 @@ module ExercisesHelper
question_comment = []
# user_score_pre = nil
if ques_type == 5
- exercise_answers = q.exercise_shixun_answers.search_shixun_answers("user_id",ex_answerer_id)
+ exercise_answers = q.exercise_shixun_answers.select{|answer| answer.user_id == ex_answerer_id}
else
- exercise_answers = q.exercise_answers.search_exercise_answer("user_id",ex_answerer_id) #试卷用户的回答
+ exercise_answers = q.exercise_answers.select{|answer| answer.user_id == ex_answerer_id} #试卷用户的回答
end
if student_status == 2 #当前为老师,或为学生且已提交
- user_score_pre = exercise_answers.score_reviewed
+ user_score_pre = exercise_answers.select{|answer| answer.score >= 0.0}
if ques_type == 4 #主观题时,且没有大于0的分数时,为空
user_score = user_score_pre.present? ? user_score_pre.pluck(:score).sum : nil
elsif ques_type == 5 || ques_type == 3
@@ -829,7 +829,7 @@ module ExercisesHelper
if ex_type == 4 #填空题/主观题/实训题有评论的
q_answer_id = exercise_answers.present? ? exercise_answers.first.id : nil
- question_comment = q.exercise_answer_comments.search_answer_comments("exercise_answer_id",q_answer_id)
+ question_comment = q.exercise_answer_comments.select{|comment| comment.exercise_answer_id == q_answer_id}
end
{
"user_score": (user_score.present? ? user_score.round(1).to_s : nil),
diff --git a/app/helpers/export_helper.rb b/app/helpers/export_helper.rb
index 05b1b2f8b..534a78dc1 100644
--- a/app/helpers/export_helper.rb
+++ b/app/helpers/export_helper.rb
@@ -286,10 +286,18 @@ module ExportHelper
@user_columns = []
ques_type_boolean = question_types.include?(4)
if ques_type_boolean #仅存在主观题或客观题的时候
- @table_columns = @table_columns + %w(客观题成绩 主观题成绩 最终成绩 开始答题时间 提交时间)
+ @table_columns = @table_columns + %w(客观题成绩 主观题成绩 最终成绩)
else
- @table_columns = @table_columns + %w(最终成绩 开始答题时间 提交时间)
+ @table_columns = @table_columns + %w(最终成绩)
end
+ for i in 1 .. exercise.exercise_questions.size
+ @table_columns = @table_columns + ["第#{i}题"]
+ end
+
+ @table_columns = @table_columns + %w(开始答题时间 提交时间)
+
+ questions = exercise.exercise_questions.includes(:exercise_answers,:exercise_shixun_answers).order("question_number ASC")
+
export_ex_users.includes(user: :user_extension).each_with_index do |e_user,index|
user_info = e_user.user
member = course.students.find_by_user_id(e_user.user_id)
@@ -312,11 +320,36 @@ module ExportHelper
user_option = [index+1,user_login,user_real_name, user_mail,
user_student_id,user_course,user_commit_stu]
if ques_type_boolean
- other_user_option = [user_obj_score,user_suj_score,user_score,user_start_time,user_end_time]
+ other_user_option = [user_obj_score,user_suj_score,user_score]
else
- other_user_option = [user_score,user_start_time,user_end_time]
+ other_user_option = [user_score]
end
- user_option = user_option + other_user_option
+
+ time_option = [user_start_time,user_end_time]
+
+ score_option = []
+ questions.each do |q|
+ q_type = q.question_type
+ if q_type == Exercise::PRACTICAL
+ answers_content = q.exercise_shixun_answers.select{|answer| answer.user_id == e_user.user_id}
+ else
+ answers_content = q.exercise_answers.select{|answer| answer.user_id == e_user.user_id}
+ end
+
+ if q_type <= Exercise::JUDGMENT || q_type == Exercise::SUBJECTIVE
+ if answers_content.present? #学生有回答时,分数已经全部存到exercise_answer 表,所以可以直接取第一个值
+ ques_score = answers_content.first.score
+ ques_score = ques_score.nil? || ques_score < 0 ? 0.0 : ques_score
+ else
+ ques_score = 0.0
+ end
+ else
+ ques_score = answers_content.select{|answer| answer.score >= 0.0}.pluck(:score).sum
+ end
+ score_option << ques_score
+ end
+
+ user_option = user_option + other_user_option + score_option + time_option
@user_columns.push(user_option)
end
end
@@ -419,7 +452,7 @@ module ExportHelper
end
end
- out_file_name = "作品附件_#{homework_common&.course&.name}_#{homework_common.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
+ out_file_name = "作品附件_#{homework_common.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
out_file_name.gsub!(" ", "-")
out_file_name.gsub!("/", "_")
out_file = find_or_pack(homework_common, homework_common.user_id, digests.sort){
diff --git a/app/helpers/homework_commons_helper.rb b/app/helpers/homework_commons_helper.rb
index cc23d05d6..efc14dc5e 100644
--- a/app/helpers/homework_commons_helper.rb
+++ b/app/helpers/homework_commons_helper.rb
@@ -222,9 +222,17 @@ module HomeworkCommonsHelper
[{ id: 0 ,name: "未评", count: homework.uncomment_count(user_id)}, {id: 1, name: "已评", count: homework.comment_count(user_id)}]
end
+ # 作品状态
+ def practice_homework_status homework, member
+ [{id: 3, name: "未通关", count: homework.un_complete_count(member)},
+ {id: 4, name: "已通关", count: homework.complete_count(member)},
+ {id: 1, name: "按时完成", count: homework.finished_count(member)},
+ {id: 2, name: "延时完成", count: homework.delay_finished_count(member)}]
+ end
+
# 作品状态
def homework_status homework, member
- [{id: 0, name: "未提交", count: homework.unfinished_count(member)},
+ [{id: 0, name: "未提交", count: homework.unfinished_count(member)},
{id: 1, name: "按时提交", count: homework.finished_count(member)},
{id: 2, name: "延时提交", count: homework.delay_finished_count(member)}]
end
diff --git a/app/libs/omniauth/strategies/qq.rb b/app/libs/omniauth/strategies/qq.rb
new file mode 100644
index 000000000..513257e3c
--- /dev/null
+++ b/app/libs/omniauth/strategies/qq.rb
@@ -0,0 +1,50 @@
+module OmniAuth
+ module Strategies
+ class QQ < OmniAuth::Strategies::OAuth2
+ option :client_options, {
+ site: 'https://graph.qq.com',
+ authorize_url: '/oauth2.0/authorize',
+ token_url: '/oauth2.0/token'
+ }
+
+ def request_phase
+ super
+ end
+
+ def authorize_params
+ super.tap do |params|
+ %w[scope client_options].each do |v|
+ if request.params[v]
+ params[v.to_sym] = request.params[v]
+ end
+ end
+ end
+ end
+
+ uid { raw_info['openid'].to_s }
+
+ info do
+ {
+ name: user_info['nickname'],
+ nickname: user_info['nickname'],
+ image: user_info['figureurl_qq_1']
+ }
+ end
+
+ extra do
+ { raw_info: user_info }
+ end
+
+ def raw_info
+ access_token.options[:mode] = :query
+ @raw_info ||= access_token.get('/oauth2.0/me').parsed
+ end
+
+ def user_info
+ access_token.options[:mode] = :query
+ params = { oauth_consumer_key: options.client_id, openid: raw_info['openid'], format: 'json' }
+ @user_info ||= access_token.get('/user/get_user_info', params: params)
+ end
+ end
+ end
+end
diff --git a/app/libs/util.rb b/app/libs/util.rb
index 72e728ab9..84f14a6c0 100644
--- a/app/libs/util.rb
+++ b/app/libs/util.rb
@@ -1,3 +1,5 @@
+require 'open-uri'
+
module Util
module_function
@@ -29,6 +31,16 @@ module Util
end
end
+ def download_file(url, save_path)
+ data = open(url, &:read)
+ file = File.new(save_path, 'w+')
+ file.binmode
+ file << data
+ file.flush
+ file.close
+ file
+ end
+
def logger_error(exception)
Rails.logger.error(exception.message)
exception.backtrace.each { |message| Rails.logger.error(message) }
diff --git a/app/libs/util/file_manage.rb b/app/libs/util/file_manage.rb
index 822bfca4f..2f87a3e86 100644
--- a/app/libs/util/file_manage.rb
+++ b/app/libs/util/file_manage.rb
@@ -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)
diff --git a/app/libs/wechat/app.rb b/app/libs/wechat/weapp.rb
similarity index 100%
rename from app/libs/wechat/app.rb
rename to app/libs/wechat/weapp.rb
diff --git a/app/libs/wechat_oauth.rb b/app/libs/wechat_oauth.rb
new file mode 100644
index 000000000..ba4baee30
--- /dev/null
+++ b/app/libs/wechat_oauth.rb
@@ -0,0 +1,13 @@
+module WechatOauth
+ class << self
+ attr_accessor :appid, :secret, :scope, :base_url
+
+ def logger
+ @_logger ||= STDOUT
+ end
+
+ def logger=(l)
+ @_logger = l
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/libs/wechat_oauth/error.rb b/app/libs/wechat_oauth/error.rb
new file mode 100644
index 000000000..ac7f5fddc
--- /dev/null
+++ b/app/libs/wechat_oauth/error.rb
@@ -0,0 +1,14 @@
+class WechatOauth::Error < StandardError
+ attr_reader :code
+
+ def initialize(code, msg)
+ super(msg)
+ @code = code
+ end
+
+ def message
+ I18n.t("oauth.wechat.#{code}")
+ rescue I18n::MissingTranslationData
+ super
+ end
+end
\ No newline at end of file
diff --git a/app/libs/wechat_oauth/service.rb b/app/libs/wechat_oauth/service.rb
new file mode 100644
index 000000000..35ef8f455
--- /dev/null
+++ b/app/libs/wechat_oauth/service.rb
@@ -0,0 +1,61 @@
+module WechatOauth::Service
+ module_function
+
+ def request(method, url, params)
+ WechatOauth.logger.info("[WechatOauth] [#{method.to_s.upcase}] #{url} || #{params}")
+
+ client = Faraday.new(url: WechatOauth.base_url)
+ response = client.public_send(method, url, params)
+ result = JSON.parse(response.body)
+
+ WechatOauth.logger.info("[WechatOauth] [#{response.status}] #{result}")
+
+ if result['errcode'].present? && result['errcode'].to_s != '0'
+ raise WechatOauth::Error.new(result['errcode'], result['errmsg'])
+ end
+
+ result
+ end
+
+ # https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
+ # response:
+ # {
+ # "access_token":"ACCESS_TOKEN",
+ # "expires_in":7200,
+ # "refresh_token":"REFRESH_TOKEN",
+ # "openid":"OPENID",
+ # "scope":"SCOPE",
+ # "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
+ # }
+ def access_token(code)
+ params = {
+ appid: WechatOauth.appid,
+ secret: WechatOauth.secret,
+ code: code,
+ grant_type: 'authorization_code'
+ }
+
+ request(:get, '/sns/oauth2/access_token', params)
+ end
+
+ # https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Authorized_Interface_Calling_UnionID.html
+ # response:
+ # {
+ # "openid":"OPENID",
+ # "nickname":"NICKNAME",
+ # "sex":1,
+ # "province":"PROVINCE",
+ # "city":"CITY",
+ # "country":"COUNTRY",
+ # "headimgurl": "http://wx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
+ # "privilege":[
+ # "PRIVILEGE1",
+ # "PRIVILEGE2"
+ # ],
+ # "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
+ #
+ # }
+ def user_info(access_token, openid)
+ request(:get, '/sns/userinfo', access_token: access_token, openid: openid)
+ end
+end
\ No newline at end of file
diff --git a/app/models/homework_common.rb b/app/models/homework_common.rb
index abe254b28..fc2dd3ea4 100644
--- a/app/models/homework_common.rb
+++ b/app/models/homework_common.rb
@@ -240,6 +240,16 @@ class HomeworkCommon < ApplicationRecord
self.teacher_works(member).delay_finished.count
end
+ # 未通关数
+ def un_complete_count member
+ teacher_works(member).count - complete_count(member)
+ end
+
+ # 通关数
+ def complete_count member
+ Myshixun.where(id: self.teacher_works(member).pluck(:myshixun_id), status: 1).count
+ end
+
# 分组作业的最大分组id
def max_group_id
self.student_works.has_committed.maximum(:group_id).to_i + 1
diff --git a/app/models/homework_group_setting.rb b/app/models/homework_group_setting.rb
index ae9491cb3..7a06d5a7a 100644
--- a/app/models/homework_group_setting.rb
+++ b/app/models/homework_group_setting.rb
@@ -6,6 +6,6 @@ class HomeworkGroupSetting < ApplicationRecord
scope :none_published, -> {where("homework_group_settings.publish_time IS NULL OR homework_group_settings.publish_time > ?", Time.now)}
scope :published_no_end, -> {where("homework_group_settings.publish_time IS NOT NULL AND homework_group_settings.publish_time < ?
and homework_group_settings.end_time > ?", Time.now, Time.now)}
- scope :none_end, -> {where("homework_group_settings.end_time IS NOT NULL AND homework_group_settings.end_time > ?", Time.now)}
+ scope :none_end, -> {where("homework_group_settings.end_time IS NULL or homework_group_settings.end_time > ?", Time.now)}
end
diff --git a/app/models/laboratory.rb b/app/models/laboratory.rb
new file mode 100644
index 000000000..53e66ece0
--- /dev/null
+++ b/app/models/laboratory.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/laboratory_setting.rb b/app/models/laboratory_setting.rb
new file mode 100644
index 000000000..32848dca2
--- /dev/null
+++ b/app/models/laboratory_setting.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/laboratory_user.rb b/app/models/laboratory_user.rb
new file mode 100644
index 000000000..be6c0c4dd
--- /dev/null
+++ b/app/models/laboratory_user.rb
@@ -0,0 +1,4 @@
+class LaboratoryUser < ApplicationRecord
+ belongs_to :laboratory
+ belongs_to :user
+end
\ No newline at end of file
diff --git a/app/models/open_user.rb b/app/models/open_user.rb
new file mode 100644
index 000000000..91228b976
--- /dev/null
+++ b/app/models/open_user.rb
@@ -0,0 +1,9 @@
+class OpenUser < ApplicationRecord
+ belongs_to :user
+
+ validates :uid, presence: true, uniqueness: { scope: :type }
+
+ def can_bind_cache_key
+ "open_user:#{type}:#{uid}:can_bind"
+ end
+end
\ No newline at end of file
diff --git a/app/models/open_users/qq.rb b/app/models/open_users/qq.rb
new file mode 100644
index 000000000..242693ce5
--- /dev/null
+++ b/app/models/open_users/qq.rb
@@ -0,0 +1,3 @@
+class OpenUsers::QQ < OpenUser
+
+end
\ No newline at end of file
diff --git a/app/models/open_users/wechat.rb b/app/models/open_users/wechat.rb
new file mode 100644
index 000000000..046b3e086
--- /dev/null
+++ b/app/models/open_users/wechat.rb
@@ -0,0 +1,3 @@
+class OpenUsers::Wechat < OpenUser
+
+end
\ No newline at end of file
diff --git a/app/models/user.rb b/app/models/user.rb
index 7bfe9c36f..2f5400051 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -28,7 +28,12 @@ class User < ApplicationRecord
MIX_PASSWORD_LIMIT = 8
+ LOGIN_CHARS = %W(2 3 4 5 6 7 8 9 a b c f e f g h i j k l m n o p q r s t u v w x y z).freeze
+
has_one :user_extension, dependent: :destroy
+ has_many :open_users, dependent: :destroy
+ has_one :wechat_open_user, class_name: 'OpenUsers::Wechat'
+ has_one :qq_open_user, class_name: 'OpenUsers::QQ'
accepts_nested_attributes_for :user_extension, update_only: true
has_many :memos, foreign_key: 'author_id'
@@ -38,7 +43,7 @@ class User < ApplicationRecord
has_many :myshixuns, :dependent => :destroy
has_many :study_shixuns, through: :myshixuns, source: :shixun # 已学习的实训
has_many :course_messages
- has_many :courses, dependent: :destroy
+ has_many :courses, foreign_key: 'tea_id', dependent: :destroy
#试卷
has_many :exercise_banks, :dependent => :destroy
@@ -628,6 +633,23 @@ class User < ApplicationRecord
admin? || business?
end
+ def self.generate_login(prefix)
+ login = prefix + LOGIN_CHARS.sample(8).join('')
+ while User.exists?(login: login)
+ login = prefix + LOGIN_CHARS.sample(8).join('')
+ end
+
+ login
+ end
+
+ def bind_open_user?(type)
+ case type
+ when 'wechat' then wechat_open_user.present?
+ when 'qq' then qq_open_user.present?
+ else false
+ end
+ end
+
protected
def validate_password_length
# 管理员的初始密码是5位
diff --git a/app/queries/admins/laboratory_query.rb b/app/queries/admins/laboratory_query.rb
new file mode 100644
index 000000000..8667bb8ed
--- /dev/null
+++ b/app/queries/admins/laboratory_query.rb
@@ -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.left_joins(:school).where(like_sql, keyword: "%#{keyword}%")
+ end
+
+ custom_sort laboratories, params[:sort_by], params[:sort_direction]
+ end
+end
\ No newline at end of file
diff --git a/app/services/admins/add_laboratory_user_service.rb b/app/services/admins/add_laboratory_user_service.rb
new file mode 100644
index 000000000..16df30880
--- /dev/null
+++ b/app/services/admins/add_laboratory_user_service.rb
@@ -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
\ No newline at end of file
diff --git a/app/services/admins/create_laboratory_service.rb b/app/services/admins/create_laboratory_service.rb
new file mode 100644
index 000000000..98300d5af
--- /dev/null
+++ b/app/services/admins/create_laboratory_service.rb
@@ -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
\ No newline at end of file
diff --git a/app/services/admins/save_laboratory_setting_service.rb b/app/services/admins/save_laboratory_setting_service.rb
new file mode 100644
index 000000000..00e202cd9
--- /dev/null
+++ b/app/services/admins/save_laboratory_setting_service.rb
@@ -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
\ No newline at end of file
diff --git a/app/services/application_service.rb b/app/services/application_service.rb
index c6f66c098..1be6896eb 100644
--- a/app/services/application_service.rb
+++ b/app/services/application_service.rb
@@ -1,3 +1,11 @@
class ApplicationService
include Callable
+
+ Error = Class.new(StandardError)
+
+ private
+
+ def strip(str)
+ str.to_s.strip.presence
+ end
end
\ No newline at end of file
diff --git a/app/services/create_bind_user_service.rb b/app/services/create_bind_user_service.rb
new file mode 100644
index 000000000..93d9d87ca
--- /dev/null
+++ b/app/services/create_bind_user_service.rb
@@ -0,0 +1,53 @@
+class CreateBindUserService < ApplicationService
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ raise Error, '系统错误' if open_user.blank?
+ raise Error, '系统错误' unless can_bind_user?
+
+ if params[:not_bind].to_s == 'true'
+ clear_can_bind_user_flag
+ return user
+ end
+
+ bind_user = User.try_to_login(params[:username], params[:password])
+ raise Error, '用户名或者密码错误' if bind_user.blank?
+ raise Error, '该账号已被绑定' if bind_user.bind_open_user?(params[:type].to_s)
+
+ ActiveRecord::Base.transaction do
+ open_user.user_id = bind_user.id
+ open_user.save!
+
+ user.user_extension.delete
+ user.delete
+ end
+
+ clear_can_bind_user_flag
+
+ bind_user
+ end
+
+ private
+
+ def open_user
+ @_open_user ||= begin
+ case params[:type].to_s
+ when 'wechat' then user.wechat_open_user
+ when 'qq' then user.qq_open_user
+ end
+ end
+ end
+
+ def can_bind_user?
+ Rails.cache.read(open_user.can_bind_cache_key).present?
+ end
+
+ def clear_can_bind_user_flag
+ Rails.cache.delete(open_user.can_bind_cache_key)
+ end
+end
\ No newline at end of file
diff --git a/app/services/oauth/create_or_find_qq_account_service.rb b/app/services/oauth/create_or_find_qq_account_service.rb
new file mode 100644
index 000000000..691764ea2
--- /dev/null
+++ b/app/services/oauth/create_or_find_qq_account_service.rb
@@ -0,0 +1,38 @@
+class Oauth::CreateOrFindQqAccountService < ApplicationService
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ new_user = false
+ # 存在该用户
+ open_user = OpenUsers::QQ.find_by(uid: params['uid'])
+ return [open_user.user, new_user] if open_user.present?
+
+ if user.blank? || !user.logged?
+ new_user = true
+ # 新用户
+ login = User.generate_login('q')
+ @user = User.new(login: login, nickname: params.dig('info', 'nickname'), type: 'User', status: User::STATUS_ACTIVE)
+ end
+
+ ActiveRecord::Base.transaction do
+ if user.new_record?
+ user.save!
+
+ gender = params.dig('extra', 'raw_info', 'gender') == '女' ? 1 : 0
+ user.create_user_extension!(gender: gender)
+ end
+
+ new_open_user = OpenUsers::QQ.create!(user: user, uid: params['uid'])
+
+ Rails.cache.write(new_open_user.can_bind_cache_key, 1, expires_in: 1.hours) if new_user # 方便后面进行账号绑定
+ end
+
+ [user, new_user]
+ end
+end
\ No newline at end of file
diff --git a/app/services/oauth/create_or_find_wechat_account_service.rb b/app/services/oauth/create_or_find_wechat_account_service.rb
new file mode 100644
index 000000000..0313054b7
--- /dev/null
+++ b/app/services/oauth/create_or_find_wechat_account_service.rb
@@ -0,0 +1,57 @@
+class Oauth::CreateOrFindWechatAccountService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ code = params['code'].to_s.strip
+ raise Error, 'Code不能为空' if code.blank?
+ new_user = false
+
+ result = WechatOauth::Service.access_token(code)
+ result = WechatOauth::Service.user_info(result['access_token'], result['openid'])
+
+ # 存在该用户
+ open_user = OpenUsers::Wechat.find_by(uid: result['unionid'])
+ return [open_user.user, new_user] if open_user.present?
+
+ if user.blank? || !user.logged?
+ new_user = true
+ # 新用户
+ login = User.generate_login('w')
+ @user = User.new(login: login, nickname: result['nickname'], type: 'User', status: User::STATUS_ACTIVE)
+ end
+
+ ActiveRecord::Base.transaction do
+ if new_user
+ user.save!
+
+ gender = result['sex'].to_i == 1 ? 0 : 1
+ user.create_user_extension!(gender: gender)
+
+ # 下载头像
+ avatar_path = Util::FileManage.source_disk_filename(user)
+ Util.download_file(result['headimgurl'], avatar_path)
+ end
+
+ new_open_user= OpenUsers::Wechat.create!(user: user, uid: result['unionid'])
+
+ Rails.cache.write(new_open_user.can_bind_cache_key, 1, expires_in: 1.hours) if new_user # 方便后面进行账号绑定
+ end
+
+ [user, new_user]
+ rescue WechatOauth::Error => ex
+ raise Error, ex.message
+ end
+
+ private
+
+ def code
+ params[:code].to_s.strip
+ end
+end
\ No newline at end of file
diff --git a/app/services/users/update_password_service.rb b/app/services/users/update_password_service.rb
index 0df32eb76..53c6f74c8 100644
--- a/app/services/users/update_password_service.rb
+++ b/app/services/users/update_password_service.rb
@@ -11,7 +11,7 @@ class Users::UpdatePasswordService < ApplicationService
def call
Users::UpdatePasswordForm.new(params).validate!
- raise Error, '旧密码不匹配' unless user.check_password?(params[:old_password])
+ raise Error, '旧密码不匹配' unless user.check_password?(params[:old_password]) || user.hashed_password.blank?
ActiveRecord::Base.transaction do
user.update!(password: params[:password])
diff --git a/app/views/admins/laboratories/index.html.erb b/app/views/admins/laboratories/index.html.erb
new file mode 100644
index 000000000..012eed792
--- /dev/null
+++ b/app/views/admins/laboratories/index.html.erb
@@ -0,0 +1,19 @@
+<% define_admin_breadcrumbs do %>
+ <% add_admin_breadcrumb('云上实验室') %>
+<% end %>
+
+
+ <%= 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' } %>
+
+
+
+ <%= render(partial: 'admins/laboratories/shared/list', locals: { laboratories: @laboratories }) %>
+
+
+<%= render 'admins/laboratories/shared/create_laboratory_modal' %>
+<%= render 'admins/laboratories/shared/add_laboratory_user_modal' %>
\ No newline at end of file
diff --git a/app/views/admins/laboratories/index.js.erb b/app/views/admins/laboratories/index.js.erb
new file mode 100644
index 000000000..dc17c6a6d
--- /dev/null
+++ b/app/views/admins/laboratories/index.js.erb
@@ -0,0 +1 @@
+$('.laboratory-list-container').html("<%= j(render partial: 'admins/laboratories/shared/list', locals: { laboratories: @laboratories }) %>");
\ No newline at end of file
diff --git a/app/views/admins/laboratories/shared/_add_laboratory_user_modal.html.erb b/app/views/admins/laboratories/shared/_add_laboratory_user_modal.html.erb
new file mode 100644
index 000000000..a13565cd6
--- /dev/null
+++ b/app/views/admins/laboratories/shared/_add_laboratory_user_modal.html.erb
@@ -0,0 +1,30 @@
+
\ No newline at end of file
diff --git a/app/views/admins/laboratories/shared/_create_laboratory_modal.html.erb b/app/views/admins/laboratories/shared/_create_laboratory_modal.html.erb
new file mode 100644
index 000000000..0a77477d3
--- /dev/null
+++ b/app/views/admins/laboratories/shared/_create_laboratory_modal.html.erb
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/app/views/admins/laboratories/shared/_laboratory_item.html.erb b/app/views/admins/laboratories/shared/_laboratory_item.html.erb
new file mode 100644
index 000000000..5dd97b549
--- /dev/null
+++ b/app/views/admins/laboratories/shared/_laboratory_item.html.erb
@@ -0,0 +1,40 @@
+<% school = laboratory.school %>
+<%= school&.name || 'EduCoder主站' %>
+
+ <% if laboratory.identifier %>
+ <%= link_to laboratory.site, "https://#{laboratory.site}", target: '_blank' %>
+ <% else %>
+ --
+ <% end %>
+
+
+ <% if school && school.identifier.present? %>
+ <%= link_to school.identifier.to_s, statistics_college_path(school.identifier), target: '_blank' %>
+ <% else %>
+ --
+ <% end %>
+
+
+
+ <% laboratory.users.each do |user| %>
+
+ <%= 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 %>
+
+ <% end %>
+
+ <% end %>
+
+
+<%= laboratory.created_at.strftime('%Y-%m-%d %H:%M') %>
+
+ <%= 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 %>
+
\ No newline at end of file
diff --git a/app/views/admins/laboratories/shared/_list.html.erb b/app/views/admins/laboratories/shared/_list.html.erb
new file mode 100644
index 000000000..33a47eed7
--- /dev/null
+++ b/app/views/admins/laboratories/shared/_list.html.erb
@@ -0,0 +1,25 @@
+
+
+
+ 单位名称
+ 域名
+ 统计链接
+ 管理员
+ <%= sort_tag('创建时间', name: 'id', path: admins_laboratories_path) %>
+ 操作
+
+
+
+ <% if laboratories.present? %>
+ <% laboratories.each do |laboratory| %>
+
+ <%= render 'admins/laboratories/shared/laboratory_item', laboratory: laboratory %>
+
+ <% end %>
+ <% else %>
+ <%= render 'admins/shared/no_data_for_table' %>
+ <% end %>
+
+
+
+<%= render partial: 'admins/shared/paginate', locals: { objects: laboratories } %>
\ No newline at end of file
diff --git a/app/views/admins/laboratory_settings/show.html.erb b/app/views/admins/laboratory_settings/show.html.erb
new file mode 100644
index 000000000..120bba6cb
--- /dev/null
+++ b/app/views/admins/laboratory_settings/show.html.erb
@@ -0,0 +1,131 @@
+<% define_admin_breadcrumbs do %>
+ <% add_admin_breadcrumb('云上实验室', admins_laboratories_path) %>
+ <% add_admin_breadcrumb('单位定制') %>
+<% end %>
+
+
+ <%= simple_form_for(@laboratory, url: admins_laboratory_laboratory_setting_path(@laboratory), method: 'patch', html: { enctype: 'multipart/form-data' }) do |f| %>
+ <% setting = @laboratory.laboratory_setting %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= javascript_void_link '保存', class: 'btn btn-primary mr-3 px-4 submit-btn' %>
+ <%= link_to '取消', admins_laboratories_path, class: 'btn btn-secondary px-4' %>
+
+ <% end %>
+
\ No newline at end of file
diff --git a/app/views/admins/laboratory_users/create.js.erb b/app/views/admins/laboratory_users/create.js.erb
new file mode 100644
index 000000000..f43fd7887
--- /dev/null
+++ b/app/views/admins/laboratory_users/create.js.erb
@@ -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 }) %>")
\ No newline at end of file
diff --git a/app/views/admins/laboratory_users/destroy.js.erb b/app/views/admins/laboratory_users/destroy.js.erb
new file mode 100644
index 000000000..16ef62910
--- /dev/null
+++ b/app/views/admins/laboratory_users/destroy.js.erb
@@ -0,0 +1,2 @@
+$.notify({ message: '操作成功' });
+$('.laboratory-list-container .laboratory-item-<%= current_laboratory.id %> .laboratory-user-item-<%= @laboratory_user.user_id %>').remove();
\ No newline at end of file
diff --git a/app/views/admins/shared/_sidebar.html.erb b/app/views/admins/shared/_sidebar.html.erb
index 553820e69..145910928 100644
--- a/app/views/admins/shared/_sidebar.html.erb
+++ b/app/views/admins/shared/_sidebar.html.erb
@@ -33,23 +33,13 @@
<%= sidebar_item_group('#schools-submenu', '单位管理', icon: 'building') do %>
<%= sidebar_item(admins_schools_path, '单位列表', icon: 'university', controller: 'admins-schools') %>
<%= sidebar_item(admins_departments_path, '部门列表', icon: 'sitemap', controller: 'admins-departments') %>
+ <%= sidebar_item(admins_laboratories_path, '云上实验室', icon: 'cloud', controller: 'admins-laboratories') %>
<% end %>
-
- <%#= sidebar_item_group('#course-submenu', '课堂+', icon: 'mortar-board') do %>
-
-
-
-
- <%# end %>
-
-
<%= sidebar_item_group('#user-submenu', '用户', icon: 'user') do %>
<%= sidebar_item(admins_users_path, '用户列表', icon: 'user', controller: 'admins-users') %>
-
-
<% end %>
diff --git a/app/views/courses/students.json.jbuilder b/app/views/courses/students.json.jbuilder
index 0b5d7fe71..5788b44f2 100644
--- a/app/views/courses/students.json.jbuilder
+++ b/app/views/courses/students.json.jbuilder
@@ -1,7 +1,7 @@
json.students do
json.array! @students do |student|
json.user_id student.user_id
- # json.login student.user.try(:login)
+ json.login student.user.try(:login)
json.name student.user.try(:real_name)
json.name_link user_path(student.user)
json.student_id student.user.try(:student_id)
diff --git a/app/views/courses/top_banner.json.jbuilder b/app/views/courses/top_banner.json.jbuilder
index 877ffcdf1..9a0554585 100644
--- a/app/views/courses/top_banner.json.jbuilder
+++ b/app/views/courses/top_banner.json.jbuilder
@@ -15,7 +15,7 @@ json.is_admin @user_course_identity < Course::PROFESSOR
json.is_public @course.is_public == 1
json.code_halt @course.invite_code_halt == 1
json.invite_code @course.invite_code_halt == 0 ? @course.generate_invite_code : ""
-json.switch_to_student switch_student_role(@is_teacher, @course, @user)
+json.switch_to_student @switch_student
json.switch_to_teacher switch_teacher_role(@is_student, @course, @user)
json.switch_to_assistant switch_assistant_role(@is_student, @course, @user)
#json.join_course !@user.member_of_course?(@course)
diff --git a/app/views/exercises/_user_exercise_info.json.jbuilder b/app/views/exercises/_user_exercise_info.json.jbuilder
index bdac3a985..c351a9b26 100644
--- a/app/views/exercises/_user_exercise_info.json.jbuilder
+++ b/app/views/exercises/_user_exercise_info.json.jbuilder
@@ -65,7 +65,7 @@ json.exercise_questions do
shixun_type: user_ques_answers[:shixun_type],
ques_position: nil,
edit_type:nil
- if user_ques_comments.count > 0
+ if user_ques_comments.size > 0
json.question_comments do
json.partial! "exercises/exercise_comments", question_comment:user_ques_answers[:question_comment].first
end
diff --git a/app/views/exercises/common_header.json.jbuilder b/app/views/exercises/common_header.json.jbuilder
index 5d33aca66..d43d7c3f8 100644
--- a/app/views/exercises/common_header.json.jbuilder
+++ b/app/views/exercises/common_header.json.jbuilder
@@ -1,6 +1,6 @@
json.course_is_end @course.is_end # true表示已结束,false表示未结束
json.extract! @exercise, :id,:exercise_name,:exercise_description,:show_statistic
-json.time @user_left_time
+json.time (@user_left_time.to_i / 60)
json.exercise_status @ex_status
diff --git a/app/views/homework_commons/works_list.json.jbuilder b/app/views/homework_commons/works_list.json.jbuilder
index e567ea79a..839b40bfd 100644
--- a/app/views/homework_commons/works_list.json.jbuilder
+++ b/app/views/homework_commons/works_list.json.jbuilder
@@ -23,7 +23,7 @@ if @user_course_identity < Course::STUDENT
if @homework.homework_type != "practice"
json.teacher_comment teacher_comment @homework, @current_user.id
end
- json.task_status homework_status @homework, @member
+ json.task_status @homework.homework_type != "practice" ? homework_status(@homework, @member) : practice_homework_status(@homework, @member)
json.course_group_info course_group_info @course, @current_user.id
elsif @user_course_identity == Course::STUDENT
diff --git a/app/views/settings/show.json.jbuilder b/app/views/settings/show.json.jbuilder
new file mode 100644
index 000000000..1fce12b77
--- /dev/null
+++ b/app/views/settings/show.json.jbuilder
@@ -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)&.[](1..-1)
+ json.login_logo_url (setting.login_logo_url || default_setting.login_logo_url)&.[](1..-1)
+ json.tab_logo_url (setting.tab_logo_url || default_setting.tab_logo_url)&.[](1..-1)
+
+ json.navbar setting.navbar || default_setting.navbar
+
+ json.footer setting.footer || default_setting.footer
+end
\ No newline at end of file
diff --git a/app/views/users/accounts/show.json.jbuilder b/app/views/users/accounts/show.json.jbuilder
index ec81cc6bf..48d69924c 100644
--- a/app/views/users/accounts/show.json.jbuilder
+++ b/app/views/users/accounts/show.json.jbuilder
@@ -25,3 +25,5 @@ json.department_name extension&.department&.name
json.base_info_completed user.profile_completed?
json.all_certified user.all_certified?
+
+json.has_password user.hashed_password.present?
diff --git a/config/admins/sidebar.yml b/config/admins/sidebar.yml
index 30af794b7..9da34a014 100644
--- a/config/admins/sidebar.yml
+++ b/config/admins/sidebar.yml
@@ -1 +1,2 @@
-admins-mirror_scripts: 'admins-mirror_repositories'
\ No newline at end of file
+admins-mirror_scripts: 'admins-mirror_repositories'
+admins-laboratory_settings: 'admins-laboratories'
\ No newline at end of file
diff --git a/config/configuration.yml.example b/config/configuration.yml.example
index 6feee28d9..612011a7f 100644
--- a/config/configuration.yml.example
+++ b/config/configuration.yml.example
@@ -1,4 +1,13 @@
defaults: &defaults
+ oauth:
+ qq:
+ appid: 'test'
+ secret: 'test123456'
+ wechat:
+ appid: 'test'
+ secret: 'test'
+ scope: 'snsapi_login'
+ base_url: 'https://api.weixin.qq.com'
aliyun_vod:
access_key_id: 'test'
access_key_secret: 'test'
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
index d173fb9fa..a501cb14f 100644
--- a/config/initializers/inflections.rb
+++ b/config/initializers/inflections.rb
@@ -14,3 +14,8 @@
# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful'
# end
+
+ActiveSupport::Inflector.inflections do |inflect|
+ inflect.acronym 'QQ'
+ inflect.acronym 'OmniAuth'
+end
\ No newline at end of file
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
new file mode 100644
index 000000000..27ade9ed4
--- /dev/null
+++ b/config/initializers/omniauth.rb
@@ -0,0 +1,17 @@
+OmniAuth.config.add_camelization 'qq', 'QQ'
+
+oauth_config = {}
+begin
+ config = Rails.application.config_for(:configuration)
+ oauth_config = config.dig('oauth', 'qq')
+ raise 'oauth qq config missing' if oauth_config.blank?
+rescue => ex
+ raise ex if Rails.env.production?
+
+ puts %Q{\033[33m [warning] qq oauth config or configuration.yml missing,
+ please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m}
+end
+
+Rails.application.config.middleware.use OmniAuth::Builder do
+ provider :qq, oauth_config['appid'], oauth_config['secret']
+end
diff --git a/config/initializers/wechat_oauth_init.rb b/config/initializers/wechat_oauth_init.rb
new file mode 100644
index 000000000..6c7f849ec
--- /dev/null
+++ b/config/initializers/wechat_oauth_init.rb
@@ -0,0 +1,17 @@
+oauth_config = {}
+begin
+ config = Rails.application.config_for(:configuration)
+ oauth_config = config.dig('oauth', 'wechat')
+ raise 'oauth wechat config missing' if oauth_config.blank?
+rescue => ex
+ raise ex if Rails.env.production?
+
+ puts %Q{\033[33m [warning] wechat oauth config or configuration.yml missing,
+ please add it or execute 'cp config/configuration.yml.example config/configuration.yml' \033[0m}
+end
+
+WechatOauth.appid = oauth_config['appid']
+WechatOauth.secret = oauth_config['secret']
+WechatOauth.scope = oauth_config['scope']
+WechatOauth.base_url = oauth_config['base_url']
+WechatOauth.logger = Rails.logger
diff --git a/config/locales/laboratories/zh-CN.yml b/config/locales/laboratories/zh-CN.yml
new file mode 100644
index 000000000..42127f0a1
--- /dev/null
+++ b/config/locales/laboratories/zh-CN.yml
@@ -0,0 +1,7 @@
+zh-CN:
+ activerecord:
+ models:
+ laboratory: ''
+ attributes:
+ laboratory:
+ identifier: '二级域名'
\ No newline at end of file
diff --git a/config/locales/oauth/wechat.zh-CN.yml b/config/locales/oauth/wechat.zh-CN.yml
new file mode 100644
index 000000000..12b58c513
--- /dev/null
+++ b/config/locales/oauth/wechat.zh-CN.yml
@@ -0,0 +1,4 @@
+'zh-CN':
+ oauth:
+ wechat:
+ '40029': '授权已失效,请重新授权'
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 50b61c463..32725e8e9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -824,6 +824,11 @@ Rails.application.routes.draw do
end
end
resource :template, only: [:show]
+ resource :setting, only: [:show]
+
+ get '/auth/qq/callback', to: 'oauth/qq#create'
+ get '/auth/wechat/callback', to: 'oauth/wechat#create'
+ resource :bind_user, only: [:create]
end
namespace :admins do
@@ -953,7 +958,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 +976,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
diff --git a/db/migrate/20190821054352_create_open_users.rb b/db/migrate/20190821054352_create_open_users.rb
new file mode 100644
index 000000000..f8e0aba4b
--- /dev/null
+++ b/db/migrate/20190821054352_create_open_users.rb
@@ -0,0 +1,14 @@
+class CreateOpenUsers < ActiveRecord::Migration[5.2]
+ def change
+ create_table :open_users do |t|
+ t.references :user
+
+ t.string :type
+ t.string :uid
+
+ t.timestamps
+
+ t.index [:type, :uid], unique: true
+ end
+ end
+end
diff --git a/db/migrate/20191010011844_create_laboratories.rb b/db/migrate/20191010011844_create_laboratories.rb
new file mode 100644
index 000000000..3dfb442f0
--- /dev/null
+++ b/db/migrate/20191010011844_create_laboratories.rb
@@ -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
diff --git a/db/migrate/20191010012226_create_laboratory_users.rb b/db/migrate/20191010012226_create_laboratory_users.rb
new file mode 100644
index 000000000..1b7ae762d
--- /dev/null
+++ b/db/migrate/20191010012226_create_laboratory_users.rb
@@ -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
diff --git a/db/migrate/20191010063403_create_laboratory_settings.rb b/db/migrate/20191010063403_create_laboratory_settings.rb
new file mode 100644
index 000000000..7f0a5f015
--- /dev/null
+++ b/db/migrate/20191010063403_create_laboratory_settings.rb
@@ -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
diff --git a/db/migrate/20191011025619_init_edu_coder_laboratory.rb b/db/migrate/20191011025619_init_edu_coder_laboratory.rb
new file mode 100644
index 000000000..831ca3985
--- /dev/null
+++ b/db/migrate/20191011025619_init_edu_coder_laboratory.rb
@@ -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{
+
+ }
+ config = setting.class.default_config.merge(name: 'EduCoder', footer: footer)
+ setting.config = config
+ setting.save!
+ end
+ end
+end
diff --git a/public/assets/.sprockets-manifest-4627fa5586ef7fed55ca286af7c028e9.json b/public/assets/.sprockets-manifest-4627fa5586ef7fed55ca286af7c028e9.json
index 52d2a4e9b..b8b49cc9d 100644
--- a/public/assets/.sprockets-manifest-4627fa5586ef7fed55ca286af7c028e9.json
+++ b/public/assets/.sprockets-manifest-4627fa5586ef7fed55ca286af7c028e9.json
@@ -1 +1 @@
-{"files":{"admin-cd9ca8bacc973ce2dbace30c97f6c40bc08e2c2ee44972f668e738e1902c0121.js":{"logical_path":"admin.js","mtime":"2019-09-11T16:20:07+08:00","size":4350881,"digest":"cd9ca8bacc973ce2dbace30c97f6c40bc08e2c2ee44972f668e738e1902c0121","integrity":"sha256-zZyousyXPOLbrOMMl/bEC8COLC7kSXL2aOc44ZAsASE="},"admin-a1b3356efe50ff4717cf22475639b5333c5354ba03fd107c9b7a8d4ae76f47aa.css":{"logical_path":"admin.css","mtime":"2019-09-11T16:20:07+08:00","size":773445,"digest":"a1b3356efe50ff4717cf22475639b5333c5354ba03fd107c9b7a8d4ae76f47aa","integrity":"sha256-obM1bv5Q/0cXzyJHVjm1MzxTVLoD/RB8m3qNSudvR6o="},"font-awesome/fontawesome-webfont-7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979.eot":{"logical_path":"font-awesome/fontawesome-webfont.eot","mtime":"2019-08-14T17:22:43+08:00","size":165742,"digest":"7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979","integrity":"sha256-e/yrbbmdXPvxcFygU23ceFhUMsxfpBu9etDwCQM7KXk="},"font-awesome/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2":{"logical_path":"font-awesome/fontawesome-webfont.woff2","mtime":"2019-08-14T17:22:43+08:00","size":77160,"digest":"2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe","integrity":"sha256-Kt78vAQefRj88tQXh53FoJmXqmTWdbejxLbOM9oT8/4="},"font-awesome/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff":{"logical_path":"font-awesome/fontawesome-webfont.woff","mtime":"2019-08-14T17:22:43+08:00","size":98024,"digest":"ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07","integrity":"sha256-ugxZ3rVFD1y0Gz+TYJ7i0NmVQVh33foiPoqKdTNHTwc="},"font-awesome/fontawesome-webfont-aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8.ttf":{"logical_path":"font-awesome/fontawesome-webfont.ttf","mtime":"2019-08-14T17:22:43+08:00","size":165548,"digest":"aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8","integrity":"sha256-qljzPyOaD7AvXHpsRcBD16msmgkzNYBmlOzW1O3A1qg="},"font-awesome/fontawesome-webfont-ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4.svg":{"logical_path":"font-awesome/fontawesome-webfont.svg","mtime":"2019-08-14T17:22:43+08:00","size":444379,"digest":"ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4","integrity":"sha256-rWFXkmwWIrpOHQPUePFUE2hSS/xG9R5C/g2UX37zI+Q="},"college-18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287.js":{"logical_path":"college.js","mtime":"2019-09-26T14:40:40+08:00","size":3352744,"digest":"18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287","integrity":"sha256-GPXoQAMxY06JijWswhh4FcCWwl4Kt0q6NBrpFhZs0oc="},"college-944d4273f62c7538368b9017fdd3387b5e3bea31a87873770eb231324546d4d9.css":{"logical_path":"college.css","mtime":"2019-09-11T16:20:07+08:00","size":546841,"digest":"944d4273f62c7538368b9017fdd3387b5e3bea31a87873770eb231324546d4d9","integrity":"sha256-lE1Cc/YsdTg2i5AX/dM4e1476jGoeHN3DrIxMkVG1Nk="},"logo-7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423.png":{"logical_path":"logo.png","mtime":"2019-09-03T08:55:53+08:00","size":2816,"digest":"7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423","integrity":"sha256-f/ESVocJv5f5iY/ockm3qPIA/x9I1TfYWvhyFfGHBCM="},"application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js":{"logical_path":"application.js","mtime":"2019-09-26T14:40:40+08:00","size":600706,"digest":"9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb","integrity":"sha256-nPvD15JZmh0N5ce4QgnhwrLmAzbw8B4Z8FgWY5GHCPs="},"application-5eb87c6e13676d0183317debce17fade27e68c4acee28c419438da15d53c94f2.css":{"logical_path":"application.css","mtime":"2019-09-11T16:20:07+08:00","size":1844002,"digest":"5eb87c6e13676d0183317debce17fade27e68c4acee28c419438da15d53c94f2","integrity":"sha256-Xrh8bhNnbQGDMX3rzhf63ifmjErO4oxBlDjaFdU8lPI="},"admin-c9e5ebe6191548550e27514196ea125cfbb402820ec125a0c9acf99d2d378fe4.js":{"logical_path":"admin.js","mtime":"2019-09-21T15:28:08+08:00","size":4382031,"digest":"c9e5ebe6191548550e27514196ea125cfbb402820ec125a0c9acf99d2d378fe4","integrity":"sha256-yeXr5hkVSFUOJ1FBluoSXPu0AoIOwSWgyaz5nS03j+Q="},"admin-59c59f8cae8bef4a8359286c985458110c9d03ea121516595c988943f4717c38.css":{"logical_path":"admin.css","mtime":"2019-09-21T14:49:04+08:00","size":840093,"digest":"59c59f8cae8bef4a8359286c985458110c9d03ea121516595c988943f4717c38","integrity":"sha256-WcWfjK6L70qDWShsmFRYEQydA+oSFRZZXJiJQ/RxfDg="},"college-38f953d6ba5b85d3fab63cb3c2bbf0d057ccc6454d07cfaafac3b06da37b8437.css":{"logical_path":"college.css","mtime":"2019-09-16T13:56:09+08:00","size":579109,"digest":"38f953d6ba5b85d3fab63cb3c2bbf0d057ccc6454d07cfaafac3b06da37b8437","integrity":"sha256-OPlT1rpbhdP6tjyzwrvw0FfMxkVNB8+q+sOwbaN7hDc="},"application-646b1158a4e8c1f13e684d6fe9025abc75f8d3ba5256e440802c0398223374f3.css":{"logical_path":"application.css","mtime":"2019-09-21T14:49:04+08:00","size":1988767,"digest":"646b1158a4e8c1f13e684d6fe9025abc75f8d3ba5256e440802c0398223374f3","integrity":"sha256-ZGsRWKTowfE+aE1v6QJavHX407pSVuRAgCwDmCIzdPM="},"admin-a47e37c0ec7cf5f22380249776d1e82d65b6b6aa272ed7389185aa200fa40751.js":{"logical_path":"admin.js","mtime":"2019-09-25T15:33:05+08:00","size":4383107,"digest":"a47e37c0ec7cf5f22380249776d1e82d65b6b6aa272ed7389185aa200fa40751","integrity":"sha256-pH43wOx89fIjgCSXdtHoLWW2tqonLtc4kYWqIA+kB1E="},"admin-432c4eac09b036c57ff1e88d902b8aa7df81164e4b419bac557cf1366c1d3ad9.js":{"logical_path":"admin.js","mtime":"2019-09-25T15:35:20+08:00","size":4383103,"digest":"432c4eac09b036c57ff1e88d902b8aa7df81164e4b419bac557cf1366c1d3ad9","integrity":"sha256-QyxOrAmwNsV/8eiNkCuKp9+BFk5LQZusVXzxNmwdOtk="},"admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js":{"logical_path":"admin.js","mtime":"2019-09-30T14:43:41+08:00","size":4387200,"digest":"978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a","integrity":"sha256-l45c5gf3fCaBShdPSA2nmsJGwiAYaO+EZUqgO7Zye1o="},"admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css":{"logical_path":"admin.css","mtime":"2019-09-30T14:43:41+08:00","size":842269,"digest":"896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7","integrity":"sha256-iWKB9HMXIrDAhNuxryHQ80pbwULViv9Xs5GGSrcd3Kc="},"application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css":{"logical_path":"application.css","mtime":"2019-09-30T14:43:41+08:00","size":1993118,"digest":"97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd","integrity":"sha256-l/MT6bt9JUdmSffXIVlZz0IUgP0KN4XRlWlTv5Sh6L0="}},"assets":{"admin.js":"admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js","admin.css":"admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css","font-awesome/fontawesome-webfont.eot":"font-awesome/fontawesome-webfont-7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979.eot","font-awesome/fontawesome-webfont.woff2":"font-awesome/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2","font-awesome/fontawesome-webfont.woff":"font-awesome/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff","font-awesome/fontawesome-webfont.ttf":"font-awesome/fontawesome-webfont-aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8.ttf","font-awesome/fontawesome-webfont.svg":"font-awesome/fontawesome-webfont-ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4.svg","college.js":"college-18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287.js","college.css":"college-38f953d6ba5b85d3fab63cb3c2bbf0d057ccc6454d07cfaafac3b06da37b8437.css","logo.png":"logo-7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423.png","application.js":"application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js","application.css":"application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css"}}
\ No newline at end of file
+{"files":{"admin-cd9ca8bacc973ce2dbace30c97f6c40bc08e2c2ee44972f668e738e1902c0121.js":{"logical_path":"admin.js","mtime":"2019-09-11T16:20:07+08:00","size":4350881,"digest":"cd9ca8bacc973ce2dbace30c97f6c40bc08e2c2ee44972f668e738e1902c0121","integrity":"sha256-zZyousyXPOLbrOMMl/bEC8COLC7kSXL2aOc44ZAsASE="},"admin-a1b3356efe50ff4717cf22475639b5333c5354ba03fd107c9b7a8d4ae76f47aa.css":{"logical_path":"admin.css","mtime":"2019-09-11T16:20:07+08:00","size":773445,"digest":"a1b3356efe50ff4717cf22475639b5333c5354ba03fd107c9b7a8d4ae76f47aa","integrity":"sha256-obM1bv5Q/0cXzyJHVjm1MzxTVLoD/RB8m3qNSudvR6o="},"font-awesome/fontawesome-webfont-7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979.eot":{"logical_path":"font-awesome/fontawesome-webfont.eot","mtime":"2019-08-14T17:22:43+08:00","size":165742,"digest":"7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979","integrity":"sha256-e/yrbbmdXPvxcFygU23ceFhUMsxfpBu9etDwCQM7KXk="},"font-awesome/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2":{"logical_path":"font-awesome/fontawesome-webfont.woff2","mtime":"2019-08-14T17:22:43+08:00","size":77160,"digest":"2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe","integrity":"sha256-Kt78vAQefRj88tQXh53FoJmXqmTWdbejxLbOM9oT8/4="},"font-awesome/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff":{"logical_path":"font-awesome/fontawesome-webfont.woff","mtime":"2019-08-14T17:22:43+08:00","size":98024,"digest":"ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07","integrity":"sha256-ugxZ3rVFD1y0Gz+TYJ7i0NmVQVh33foiPoqKdTNHTwc="},"font-awesome/fontawesome-webfont-aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8.ttf":{"logical_path":"font-awesome/fontawesome-webfont.ttf","mtime":"2019-08-14T17:22:43+08:00","size":165548,"digest":"aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8","integrity":"sha256-qljzPyOaD7AvXHpsRcBD16msmgkzNYBmlOzW1O3A1qg="},"font-awesome/fontawesome-webfont-ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4.svg":{"logical_path":"font-awesome/fontawesome-webfont.svg","mtime":"2019-08-14T17:22:43+08:00","size":444379,"digest":"ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4","integrity":"sha256-rWFXkmwWIrpOHQPUePFUE2hSS/xG9R5C/g2UX37zI+Q="},"college-18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287.js":{"logical_path":"college.js","mtime":"2019-09-26T14:40:40+08:00","size":3352744,"digest":"18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287","integrity":"sha256-GPXoQAMxY06JijWswhh4FcCWwl4Kt0q6NBrpFhZs0oc="},"college-944d4273f62c7538368b9017fdd3387b5e3bea31a87873770eb231324546d4d9.css":{"logical_path":"college.css","mtime":"2019-09-11T16:20:07+08:00","size":546841,"digest":"944d4273f62c7538368b9017fdd3387b5e3bea31a87873770eb231324546d4d9","integrity":"sha256-lE1Cc/YsdTg2i5AX/dM4e1476jGoeHN3DrIxMkVG1Nk="},"logo-7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423.png":{"logical_path":"logo.png","mtime":"2019-09-03T08:55:53+08:00","size":2816,"digest":"7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423","integrity":"sha256-f/ESVocJv5f5iY/ockm3qPIA/x9I1TfYWvhyFfGHBCM="},"application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js":{"logical_path":"application.js","mtime":"2019-09-26T14:40:40+08:00","size":600706,"digest":"9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb","integrity":"sha256-nPvD15JZmh0N5ce4QgnhwrLmAzbw8B4Z8FgWY5GHCPs="},"application-5eb87c6e13676d0183317debce17fade27e68c4acee28c419438da15d53c94f2.css":{"logical_path":"application.css","mtime":"2019-09-11T16:20:07+08:00","size":1844002,"digest":"5eb87c6e13676d0183317debce17fade27e68c4acee28c419438da15d53c94f2","integrity":"sha256-Xrh8bhNnbQGDMX3rzhf63ifmjErO4oxBlDjaFdU8lPI="},"admin-c9e5ebe6191548550e27514196ea125cfbb402820ec125a0c9acf99d2d378fe4.js":{"logical_path":"admin.js","mtime":"2019-09-21T15:28:08+08:00","size":4382031,"digest":"c9e5ebe6191548550e27514196ea125cfbb402820ec125a0c9acf99d2d378fe4","integrity":"sha256-yeXr5hkVSFUOJ1FBluoSXPu0AoIOwSWgyaz5nS03j+Q="},"admin-59c59f8cae8bef4a8359286c985458110c9d03ea121516595c988943f4717c38.css":{"logical_path":"admin.css","mtime":"2019-09-21T14:49:04+08:00","size":840093,"digest":"59c59f8cae8bef4a8359286c985458110c9d03ea121516595c988943f4717c38","integrity":"sha256-WcWfjK6L70qDWShsmFRYEQydA+oSFRZZXJiJQ/RxfDg="},"college-38f953d6ba5b85d3fab63cb3c2bbf0d057ccc6454d07cfaafac3b06da37b8437.css":{"logical_path":"college.css","mtime":"2019-09-16T13:56:09+08:00","size":579109,"digest":"38f953d6ba5b85d3fab63cb3c2bbf0d057ccc6454d07cfaafac3b06da37b8437","integrity":"sha256-OPlT1rpbhdP6tjyzwrvw0FfMxkVNB8+q+sOwbaN7hDc="},"application-646b1158a4e8c1f13e684d6fe9025abc75f8d3ba5256e440802c0398223374f3.css":{"logical_path":"application.css","mtime":"2019-09-21T14:49:04+08:00","size":1988767,"digest":"646b1158a4e8c1f13e684d6fe9025abc75f8d3ba5256e440802c0398223374f3","integrity":"sha256-ZGsRWKTowfE+aE1v6QJavHX407pSVuRAgCwDmCIzdPM="},"admin-a47e37c0ec7cf5f22380249776d1e82d65b6b6aa272ed7389185aa200fa40751.js":{"logical_path":"admin.js","mtime":"2019-09-25T15:33:05+08:00","size":4383107,"digest":"a47e37c0ec7cf5f22380249776d1e82d65b6b6aa272ed7389185aa200fa40751","integrity":"sha256-pH43wOx89fIjgCSXdtHoLWW2tqonLtc4kYWqIA+kB1E="},"admin-432c4eac09b036c57ff1e88d902b8aa7df81164e4b419bac557cf1366c1d3ad9.js":{"logical_path":"admin.js","mtime":"2019-09-25T15:35:20+08:00","size":4383103,"digest":"432c4eac09b036c57ff1e88d902b8aa7df81164e4b419bac557cf1366c1d3ad9","integrity":"sha256-QyxOrAmwNsV/8eiNkCuKp9+BFk5LQZusVXzxNmwdOtk="},"admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js":{"logical_path":"admin.js","mtime":"2019-09-30T14:43:41+08:00","size":4387200,"digest":"978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a","integrity":"sha256-l45c5gf3fCaBShdPSA2nmsJGwiAYaO+EZUqgO7Zye1o="},"admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css":{"logical_path":"admin.css","mtime":"2019-09-30T14:43:41+08:00","size":842269,"digest":"896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7","integrity":"sha256-iWKB9HMXIrDAhNuxryHQ80pbwULViv9Xs5GGSrcd3Kc="},"application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css":{"logical_path":"application.css","mtime":"2019-09-30T14:43:41+08:00","size":1993118,"digest":"97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd","integrity":"sha256-l/MT6bt9JUdmSffXIVlZz0IUgP0KN4XRlWlTv5Sh6L0="},"admin-2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888.js":{"logical_path":"admin.js","mtime":"2019-10-11T14:38:33+08:00","size":4394616,"digest":"2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888","integrity":"sha256-LNsjRC+nNQJThbiPKQDfBP7zi2FTAEGm2+N17w8K6Ig="},"admin-2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc.css":{"logical_path":"admin.css","mtime":"2019-10-10T17:12:05+08:00","size":846514,"digest":"2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc","integrity":"sha256-LChUuaAhWN7VqAmq9xRKhjCxA1SrTlb+zE3/zHE3lsw="},"application-50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af.css":{"logical_path":"application.css","mtime":"2019-10-10T17:12:05+08:00","size":2001607,"digest":"50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af","integrity":"sha256-UAWa6SmGYEO0cBUShwL8+6U9MqLfFI5k4dlhwQZRxq8="}},"assets":{"admin.js":"admin-2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888.js","admin.css":"admin-2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc.css","font-awesome/fontawesome-webfont.eot":"font-awesome/fontawesome-webfont-7bfcab6db99d5cfbf1705ca0536ddc78585432cc5fa41bbd7ad0f009033b2979.eot","font-awesome/fontawesome-webfont.woff2":"font-awesome/fontawesome-webfont-2adefcbc041e7d18fcf2d417879dc5a09997aa64d675b7a3c4b6ce33da13f3fe.woff2","font-awesome/fontawesome-webfont.woff":"font-awesome/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff","font-awesome/fontawesome-webfont.ttf":"font-awesome/fontawesome-webfont-aa58f33f239a0fb02f5c7a6c45c043d7a9ac9a093335806694ecd6d4edc0d6a8.ttf","font-awesome/fontawesome-webfont.svg":"font-awesome/fontawesome-webfont-ad6157926c1622ba4e1d03d478f1541368524bfc46f51e42fe0d945f7ef323e4.svg","college.js":"college-18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287.js","college.css":"college-38f953d6ba5b85d3fab63cb3c2bbf0d057ccc6454d07cfaafac3b06da37b8437.css","logo.png":"logo-7ff112568709bf97f9898fe87249b7a8f200ff1f48d537d85af87215f1870423.png","application.js":"application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js","application.css":"application-50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af.css"}}
\ No newline at end of file
diff --git a/public/assets/admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css b/public/assets/admin-2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc.css
similarity index 99%
rename from public/assets/admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css
rename to public/assets/admin-2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc.css
index fe1e75888..5b6bef77a 100644
--- a/public/assets/admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css
+++ b/public/assets/admin-2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc.css
@@ -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;
diff --git a/public/assets/admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css.gz b/public/assets/admin-2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc.css.gz
similarity index 78%
rename from public/assets/admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css.gz
rename to public/assets/admin-2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc.css.gz
index 581305db5..bfac93da9 100644
Binary files a/public/assets/admin-896281f4731722b0c084dbb1af21d0f34a5bc142d58aff57b391864ab71ddca7.css.gz and b/public/assets/admin-2c2854b9a02158ded5a809aaf7144a8630b10354ab4e56fecc4dffcc713796cc.css.gz differ
diff --git a/public/assets/admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js b/public/assets/admin-2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888.js
similarity index 99%
rename from public/assets/admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js
rename to public/assets/admin-2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888.js
index 49e1025c7..6203a3ce8 100644
--- a/public/assets/admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js
+++ b/public/assets/admin-2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888.js
@@ -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');
diff --git a/public/assets/admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js.gz b/public/assets/admin-2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888.js.gz
similarity index 98%
rename from public/assets/admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js.gz
rename to public/assets/admin-2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888.js.gz
index 13e36b4f6..492769a78 100644
Binary files a/public/assets/admin-978e5ce607f77c26814a174f480da79ac246c2201868ef84654aa03bb6727b5a.js.gz and b/public/assets/admin-2cdb23442fa735025385b88f2900df04fef38b61530041a6dbe375ef0f0ae888.js.gz differ
diff --git a/public/assets/application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css b/public/assets/application-50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af.css
similarity index 99%
rename from public/assets/application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css
rename to public/assets/application-50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af.css
index c1d8ae955..f62f2f56d 100644
--- a/public/assets/application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css
+++ b/public/assets/application-50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af.css
@@ -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;
diff --git a/public/assets/application-50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af.css.gz b/public/assets/application-50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af.css.gz
new file mode 100644
index 000000000..f5162f95a
Binary files /dev/null and b/public/assets/application-50059ae929866043b47015128702fcfba53d32a2df148e64e1d961c10651c6af.css.gz differ
diff --git a/public/assets/application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css.gz b/public/assets/application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css.gz
deleted file mode 100644
index 60ba5db0b..000000000
Binary files a/public/assets/application-97f313e9bb7d25476649f7d7215959cf421480fd0a3785d1956953bf94a1e8bd.css.gz and /dev/null differ
diff --git a/public/assets/application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js.gz b/public/assets/application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js.gz
index af63ccbfc..d17b8f444 100644
Binary files a/public/assets/application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js.gz and b/public/assets/application-9cfbc3d792599a1d0de5c7b84209e1c2b2e60336f0f01e19f0581663918708fb.js.gz differ
diff --git a/public/assets/college-18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287.js.gz b/public/assets/college-18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287.js.gz
index 3a4c01edc..d4b8b22dc 100644
Binary files a/public/assets/college-18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287.js.gz and b/public/assets/college-18f5e8400331634e898a35acc2187815c096c25e0ab74aba341ae916166cd287.js.gz differ
diff --git a/public/react/config/webpack.config.dev.js b/public/react/config/webpack.config.dev.js
index fe525154f..ef38a18f8 100644
--- a/public/react/config/webpack.config.dev.js
+++ b/public/react/config/webpack.config.dev.js
@@ -32,7 +32,7 @@ module.exports = {
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s
// devtool: "cheap-module-eval-source-map",
// 开启调试
- devtool: "source-map", // 开启调试
+ //devtool: "source-map", // 开启调试
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
diff --git a/public/react/public/index.html b/public/react/public/index.html
index 321fbe0e5..f6eef196a 100755
--- a/public/react/public/index.html
+++ b/public/react/public/index.html
@@ -8,7 +8,9 @@
-
+
+
+
diff --git a/public/react/src/App.js b/public/react/src/App.js
index 9b85acfa8..34b5d1a4f 100644
--- a/public/react/src/App.js
+++ b/public/react/src/App.js
@@ -64,6 +64,18 @@ const EducoderLogin = Loadable({
loader: () => import('./modules/login/EducoderLogin'),
loading: Loading,
})
+
+//微信登录
+const Otherlogin=Loadable({
+ loader: () => import('./modules/login/Otherlogin'),
+ loading: Loading,
+})
+
+const Otherloginstart=Loadable({
+ loader: () => import('./modules/login/Otherloginstart'),
+ loading: Loading,
+})
+
const TestIndex = Loadable({
loader: () => import('./modules/test'),
loading: Loading,
@@ -406,6 +418,12 @@ class App extends Component {
+
+
{
diff --git a/public/react/src/common/course/WordsBtn.js b/public/react/src/common/course/WordsBtn.js
index 85a85cfb6..68b278507 100644
--- a/public/react/src/common/course/WordsBtn.js
+++ b/public/react/src/common/course/WordsBtn.js
@@ -8,20 +8,20 @@ class WordsBtn extends Component {
}
render() {
- let{to, href,targets, style2 }=this.props
+ let{to, href,targets, style2, style, className, ...others }=this.props
return(
{
to==undefined&&targets==undefined ?
{this.props.children} :
targets!=undefined? {this.props.children}
:
{this.props.children}
}
diff --git a/public/react/src/modules/courses/ListPageIndex.js b/public/react/src/modules/courses/ListPageIndex.js
index 730a1bdce..8c5652838 100644
--- a/public/react/src/modules/courses/ListPageIndex.js
+++ b/public/react/src/modules/courses/ListPageIndex.js
@@ -33,6 +33,12 @@ const StudentsList= Loadable({
loader: () => import('./members/studentsList'),
loading: Loading,
});
+//分班列表
+const CourseGroupList= Loadable({
+ loader: () => import('./members/CourseGroupList'),
+ loading: Loading,
+});
+
const Eduinforms= Loadable({
loader: () => import('./gradinforms/Eduinforms.js'),
loading: Loading,
@@ -234,7 +240,7 @@ class ListPageIndex extends Component{
>
( )
+ (props) => ( )
}
>
diff --git a/public/react/src/modules/courses/Resource/Fileslistitem.js b/public/react/src/modules/courses/Resource/Fileslistitem.js
index 418c201b5..d364f7ada 100644
--- a/public/react/src/modules/courses/Resource/Fileslistitem.js
+++ b/public/react/src/modules/courses/Resource/Fileslistitem.js
@@ -275,25 +275,26 @@ class Fileslistitem extends Component{
`
}
- {discussMessage.course_groups.length===0?"":
-
- {discussMessage.course_groups.map((item,key)=>{
- return(
-
- {item.course_group_name}
- 将发布于 { moment(item.course_group_publish_time).format('YYYY-MM-DD HH:mm')}
-
- )
- })}
-
- }
+ {/*资源分班*/}
+ {/*{discussMessage.course_groups.length===0?"":*/}
+ {/**/}
+ {/*{discussMessage.course_groups.map((item,key)=>{*/}
+ {/*return(*/}
+ {/*
*/}
+ {/*{item.course_group_name} */}
+ {/*将发布于 { moment(item.course_group_publish_time).format('YYYY-MM-DD HH:mm')} */}
+ {/*
*/}
+ {/*)*/}
+ {/*})}*/}
+
+ {/*}*/}
{discussMessage.author.name}
大小 {discussMessage.filesize}
下载 {discussMessage.downloads_count}
- 引用 {discussMessage.quotes}
+ {/*引用 {discussMessage.quotes} */}
{/*{moment(discussMessage.publish_time).format('YYYY-MM-DD HH:mm:ss')}*/}
{/*{moment(discussMessage.publish_time).fromNow()}*/}
diff --git a/public/react/src/modules/courses/Resource/index.js b/public/react/src/modules/courses/Resource/index.js
index 1fea29f84..3423d87f0 100644
--- a/public/react/src/modules/courses/Resource/index.js
+++ b/public/react/src/modules/courses/Resource/index.js
@@ -51,8 +51,14 @@ class Fileslists extends Component{
})
if(this.props.match.params.main_id){
this.seactall();
+ this.setState({
+ child:false,
+ })
}else if(this.props.match.params.Id){
this.seactall(parseInt(this.props.match.params.Id),1)
+ this.setState({
+ child:true,
+ })
}
this.updadatalist();
on('updateNavSuccess', this.updateNavSuccess)
@@ -74,9 +80,12 @@ class Fileslists extends Component{
this.setState({
isSpin:true,
checkBoxValues:[],
- checkAllValue:false
+ checkAllValue:false,
})
if(this.props.match.params.main_id!=undefined){
+ this.setState({
+ child:false,
+ })
this.seactall();
}
}
@@ -84,31 +93,15 @@ class Fileslists extends Component{
this.setState({
isSpin:true,
checkBoxValues:[],
- checkAllValue:false
+ checkAllValue:false,
})
if(this.props.match.params.Id!=undefined){
+ this.setState({
+ child:true,
+ })
this.seactall(parseInt(this.props.match.params.Id),1)
}
}
- // if ( prevProps.match.params.Id != this.props.match.params.Id ||prevProps.isaloadtype!= this.props.isaloadtype) {
- // let lists=this.props.course_modules;
- // if(lists!=undefined){
- // debugger
- // let url=this.props.location.pathname;
- // lists.forEach((item,index)=>{
- // if(url===item.category_url){
- // this.seactall();
- // }
- // if(item.second_category!=undefined&&item.second_category.length!=0){
- // item.second_category.forEach((iem,key)=>{
- // if(url===iem.second_category_url){
- // this.seactall(parseInt(this.props.match.params.Id),2);
- // }
- // })
- // }
- // })
- // }
- // }
}
updadatalist=(id)=>{
@@ -188,23 +181,27 @@ class Fileslists extends Component{
course_second_category_id:id
}
}).then((result)=>{
- // console.log(result)
-
- if(result.status===200){
- if(result.data.status===0){
- let list=result.data.data;
- this.setState({
- total_count:list.total_count,
- publish_count:list.publish_count,
- unpublish_count:list.unpublish_count,
- files:list.files,
- filesId:list.id,
- name:list.name,
- course_is_public:result.data.data.course_is_public,
- isSpin:false
- })
- }
- }
+ if(result!=undefined){
+ if(result.status===200){
+ if(result.data.status===0){
+ let list=result.data.data;
+ this.setState({
+ total_count:list.total_count,
+ publish_count:list.publish_count,
+ unpublish_count:list.unpublish_count,
+ files:list.files,
+ filesId:list.id,
+ name:list.name,
+ course_is_public:result.data.data.course_is_public,
+ isSpin:false
+ })
+ }
+ }
+ }else{
+ this.setState({
+ isSpin:false
+ })
+ }
}).catch((error)=>{
console.log(error)
this.setState({
@@ -399,11 +396,16 @@ class Fileslists extends Component{
}
addDir = () => {
- let {filesId}=this.state;
+ let {filesId,course_modules}=this.state;
this.setState({
checkBoxValues:[]
})
- trigger('attachmentAddlog', parseInt(filesId))
+ if(parseInt(this.props.match.params.main_id)!=parseInt(this.props.coursesids)){
+ trigger('attachmentAddlog', parseInt( course_modules&&course_modules.course_modules[0].id))
+ }else{
+ trigger('attachmentAddlog', parseInt(filesId))
+ }
+
}
editDir = (name) => {
@@ -677,11 +679,13 @@ class Fileslists extends Component{
course_modules,
shixunmodal,
course_is_public,
- filesId
+ filesId,
+ child
} = this.state;
let category_id= this.props.match.params.category_id;
+
return(
@@ -766,7 +770,7 @@ class Fileslists extends Component{
has_course_groups={this.state.has_course_groups}
attachmentId={this.state.coursesecondcategoryid}
/>:""}
-
+ {/*设置资源*/}
{Settingtype&&Settingtype===true? :""}
- {this.props.isAdmin()?parseInt(this.props.match.params.main_id)===parseInt(this.props.coursesids)?this.addDir()} className={"mr30 font-16"}>添加目录 :"":""}
+ {/*{this.props.isAdmin()?parseInt(this.props.match.params.main_id)===parseInt(this.props.coursesids)?this.addDir()} className={"mr30 font-16"}>新建目录 :"":""}*/}
+ {this.props.isAdmin()?this.addDir()} className={"mr30 font-16"}>新建目录 :""}
{this.props.isAdmin()?parseInt(this.props.match.params.main_id)!=parseInt(this.props.coursesids)?this.editDir(name)} className={"mr30 font-16"}>目录重命名 :"":""}
{this.props.isAdmin()||this.props.isStudent() ? this.addResource()}>选用资源 :""}
@@ -869,7 +874,7 @@ class Fileslists extends Component{
{/*})}*/}
{this.props.isAdmin()?parseInt(this.props.match.params.main_id)===filesId&&filesId?
- this.addDir()}>添加目录
+ this.addDir()}>新建目录
:"":""}
@@ -885,7 +890,7 @@ class Fileslists extends Component{
{/*className={sorttype === 'created_on'?"none":""} className={sorttype === 'quotes'?"none":""} className={sorttype === 'downloads'?"none":""} */}
this.onSortTypeChange('created_on')}>更新时间排序
this.onSortTypeChange('downloads')}>下载次数排序
- this.onSortTypeChange('quotes')}>引用次数排序
+ {/* this.onSortTypeChange('quotes')}>引用次数排序 */}
:""}
diff --git a/public/react/src/modules/courses/boards/BoardsNew.js b/public/react/src/modules/courses/boards/BoardsNew.js
index 0222907d3..9ae74699f 100644
--- a/public/react/src/modules/courses/boards/BoardsNew.js
+++ b/public/react/src/modules/courses/boards/BoardsNew.js
@@ -287,11 +287,12 @@ class BoardsNew extends Component{
const isAdmin = this.props.isAdmin()
const courseId=this.props.match.params.coursesId;
const boardId = this.props.match.params.boardId
- const isCourseEnd = this.props.isCourseEnd()
+ const isCourseEnd = this.props.isCourseEnd();
+ document.title=this.props.coursedata&&this.props.coursedata.name;
return(
this.refs['addDirModal'].open()}>
- 添加目录
+ 新建目录
}
diff --git a/public/react/src/modules/courses/boards/TopicDetail.js b/public/react/src/modules/courses/boards/TopicDetail.js
index 8ecc85565..d42b68c17 100644
--- a/public/react/src/modules/courses/boards/TopicDetail.js
+++ b/public/react/src/modules/courses/boards/TopicDetail.js
@@ -526,8 +526,10 @@ class TopicDetail extends Component {
const isAdmin = this.props.isAdmin()
// TODO 图片上传地址
const courseId=this.props.match.params.coursesId;
- const boardId = this.props.match.params.boardId
- const isCourseEnd = this.props.isCourseEnd()
+ const boardId = this.props.match.params.boardId;
+ const isCourseEnd = this.props.isCourseEnd();
+
+ document.title=this.props.coursedata&&this.props.coursedata.name;
return (
{/* fl with100 */}
:""
+ }
+ {
+ this.props.OneSelftype===true?
+
+
+
+ { this.props.usingCheckBeforePost ?
+
+
+ 发布设置均可修改,
+
+ 点击修改
+
+
+
+ 此设置将对所有分班生效
+
+ :
+
+
+ {this.props.Topval}
+ {this.props.Topvalright}
+
+
+ {this.props.Botvalleft===undefined?"":"{this.props.Botvalleft}" }
+ {this.props.Botval}
+
+ }
+
+
+ {this.props.starttime===undefined||
+ this.props.starttime===""?""
+ :
+
+ 发布时间:
+ {this.props.starttime}
+ {this.props.modaltype===undefined||this.props.modaltype===2?
+ {/*{this.props.endtime}*/}
+ 截止时间:
+
+ {this.state.endtimetype===true?{this.state.endtimetypevalue}
:""}
+ :""}
+
}
+ {/* usingCheckBeforePost 为true的时候 全选所有分班 */}
+
+
+ {this.props.modaltype===undefined||this.props.modaltype===2
+ || this.props.usingCheckBeforePost ?"":
+
+ 分班名称
+
+ 截止时间
+
+ }
+ {this.props.modaltype===undefined||this.props.modaltype===2
+ || this.props.usingCheckBeforePost ?"":
+ }
+
+
+
+
+ :""}
+
+ )
+ }
+}
+export default OneSelfOrderModal;
\ No newline at end of file
diff --git a/public/react/src/modules/courses/coursesPublic/SelectSetting.js b/public/react/src/modules/courses/coursesPublic/SelectSetting.js
index 37b22c205..573be7fb4 100644
--- a/public/react/src/modules/courses/coursesPublic/SelectSetting.js
+++ b/public/react/src/modules/courses/coursesPublic/SelectSetting.js
@@ -1,5 +1,5 @@
import React,{ Component } from "react";
-import { Modal,Checkbox,Select,Input,Upload,Button,Icon,message,DatePicker,Tooltip} from "antd";
+import { Modal,Checkbox,Select,Input,Upload,Button,Icon,message,DatePicker,Tooltip,Radio} from "antd";
import axios from'axios';
import {getUrl,handleDateString,appendFileSizeToUploadFileAll} from 'educoder';
import locale from 'antd/lib/date-picker/locale/zh_CN';
@@ -36,9 +36,9 @@ class Selectsetting extends Component{
course_groups:undefined,
attachment_histories:undefined,
datatime:undefined,
- unified_setting:true,
fileList:[],
- fileListtype:false
+ fileListtype:false,
+ is_public:false
}
}
@@ -58,24 +58,6 @@ class Selectsetting extends Component{
let newcourse_groups=[];
let list =response.data.course_groups;
- // let list=[
- // {
- // "course_group_id": 820,
- // "course_group_name": "示例A班",
- // "course_group_publish_time": "2019-04-18T17:00:00.000+08:00"
- // },
- // {
- // "course_group_id": 821,
- // "course_group_name": "示例B班",
- // "course_group_publish_time": "2019-04-19T19:00:00.000+08:00"
- // },
- // {
- // "course_group_id": 822,
- // "course_group_name": "示例C班",
- // "course_group_publish_time": "2019-04-10T19:00:00.000+08:00"
- // }
- // ]
-
if(list.length!=0){
list.forEach((item,key)=>{
newcourse_groups.push ({
@@ -96,7 +78,6 @@ class Selectsetting extends Component{
datalist:response.data,
description: response.data.description,
is_public: response.data.is_public,
- unified_setting: response.data.unified_setting,
datatime:response.data.publish_time,
// is_public:response.data.course_groups,
//attachment_histories:response.data.attachment_histories
@@ -145,21 +126,6 @@ class Selectsetting extends Component{
this.getalldata()
}
- }
- onChangepublics=(e)=>{
- console.log(e.target.checked)
- this.setState({
- is_public:e.target.checked
- })
-
- }
-
- onChangesettings=(e)=>{
- console.log(e.target.checked)
- this.setState({
- unified_setting:e.target.checked
- })
-
}
settextarea=(e)=>{
@@ -180,7 +146,7 @@ class Selectsetting extends Component{
}
savecouseShixunModal=()=>{
- let {fileList,is_public,unified_setting,description,datatime,course_groups}=this.state;
+ let {fileList,is_public,description,datatime,course_groups}=this.state;
let newfileList=[];
for(var list of fileList){
@@ -196,29 +162,6 @@ class Selectsetting extends Component{
return
}
- // course_groups.forEach((item,key)=>{
- // if(item.course_group_id===undefined||item.publish_time===undefined){
- // this.setState({
- // course_group_publish_timestype:true
- // })
- // return
- // }
- // })
-
- // if(unified_setting===false){
- //
- // course_groups.forEach((item,key)=>{
- // if(item.course_group_id===undefined){
- // this.setState({
- // course_group_idtypes:true
- // })
- // return
- // }
- // })
- //
- // }
-
-
let coursesId=this.props.match.params.coursesId;
let attachmentId=this.props.attachmentId;
let url="/files/"+this.props.discussMessageid+".json";
@@ -228,7 +171,6 @@ class Selectsetting extends Component{
new_attachment_id:newfileList.length===0?undefined:newfileList,
course_second_category_id:this.props.coursesidtype===undefined||this.props.coursesidtype==="node"?0:attachmentId,
is_public:is_public,
- is_unified_setting:unified_setting,
publish_time:unified_setting===true?datatime===undefined?moment(new Date()).format('YYYY-MM-DD HH'):datatime:undefined,
description:description,
course_group_publish_times:unified_setting===false?course_groups:undefined
@@ -244,7 +186,6 @@ class Selectsetting extends Component{
}
onChangeTimepublish= (date, dateString) => {
- // console.log('startValue', dateString);
this.setState({
datatime:handleDateString(dateString),
})
@@ -268,31 +209,6 @@ class Selectsetting extends Component{
}
}
- // onAttachmentRemove = (file) => {
- // // confirm({
- // // title: '确定要删除这个附件吗?',
- // // okText: '确定',
- // // cancelText: '取消',
- // // // content: 'Some descriptions',
- // // onOk: () => {
- // // this.deleteAttachment(file)
- // // },
- // // onCancel() {
- // // console.log('Cancel');
- // // },
- // // });
- // // return false;
- //
- // // this.setState({
- // // Modalstype:true,
- // // Modalstopval:'确定要删除这个附件吗?',
- // // ModalSave: ()=>this.deleteAttachment(file),
- // // ModalCancel:this.cancelAttachment
- // // })
- // // return false;
- //
- // this.deleteAttachment(file);
- // }
onAttachmentRemove = (file) => {
@@ -309,14 +225,7 @@ class Selectsetting extends Component{
fileListtype:false,
fileList:[]
})
- // this.setState((state) => {
- // const index = state.fileList.indexOf(file);
- // const newFileList = state.fileList.slice();
- // newFileList.splice(index, 1);
- // return {
- // fileList: newFileList,
- // };
- // });
+
}
}
})
@@ -332,63 +241,15 @@ class Selectsetting extends Component{
fileList:[]
})
}
- // const url = `/attachments/${file.response ? file.response.id : file.uid}.json`
- }
- onChangeTimepublishs= (date, dateString,key) => {
- let {course_groups}=this.state;
- let newgroup_publish=course_groups;
- for(var i=0; i{
- let {course_groups}=this.state;
- let newgroup_publish=course_groups;
- for(var i=0; i{
- let newlist=this.state.course_groups;
- newlist.splice(key,1);
- this.setState({
- course_groups:newlist
- })
- }
-
- addgrouppublish=()=>{
- let newlist=this.state.course_groups;
- newlist.push( {
- course_group_id : undefined,
- publish_time :""
- // moment(new Date()).format('YYYY-MM-DD HH:mm')
- })
- this.setState({
- course_groups:newlist
- })
- }
render(){
- let {is_public,unified_setting,course_groups,datatime,description,datalist,course_group_publish_timestype}=this.state;
+ let {datatime,description,datalist}=this.state;
const uploadProps = {
width: 600,
- // https://github.com/ant-design/ant-design/issues/15505
- // showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。
- // showUploadList: false,
action: `${getUrl()}/api/attachments.json`,
onChange: this.handleChange,
onRemove: this.onAttachmentRemove,
@@ -401,9 +262,11 @@ class Selectsetting extends Component{
return isLt150M;
},
};
-
-
- // console.log(this.props.has_course_groups)
+ const radioStyle = {
+ display: 'block',
+ height: '30px',
+ lineHeight: '30px',
+ };
return(
*/}
- {/*{this.state.fileList.length===0?"":this.state.fileList.map((item,key)=>{*/}
- {/*return(*/}
- {/*
*/}
- {/**/}
- {/* */}
- {/* */}
- {/**/}
- {/*{item.name}*/}
- {/* */}
- {/**/}
- {/*{item.response===undefined?"":isNaN(bytesToSize(item.filesize))?"123":bytesToSize(item.filesize)}*/}
- {/* */}
- {/*this.onAttachmentRemove(item.response===undefined?"":item.response.id&&item.response.id)}> */}
- {/*
*/}
- {/*)*/}
- {/*})}*/}
-
{this.state.newfileListtypes===true?
请先上传资源
:""}
@@ -648,19 +480,14 @@ class Selectsetting extends Component{
}
`}
- {/*
*/}
- {/**/}
- {/*勾选后所有用户可见,否则仅课堂成员可见 */}
- {/* */}
- {/*
*/}
- {/*{this.props.has_course_groups&&this.props.has_course_groups===true?:""}*/}
- {this.state.course_groupss&&this.state.course_groupss.length>0?
- 统一设置 (选中则所有分班使用相同的发布设置,否则各个单独设置)
- :""}
+ {this.props.course_is_public===true?
+ 公开:
+ 选中,所有用户可见,否则课堂成员可见
+
+
:""}
+
+
+
-
- {/*this.props.has_course_groups&&this.props.has_course_groups===true?:""*/}
-
- {unified_setting===false?
- this.state.course_groups&&this.state.course_groups.map((item,key)=>{
- return(
-
-
-
- this.selectassigngroups(e,index,key)}
- >
- { this.state.course_groupss&&this.state.course_groupss.map((item,key)=>{
- return(
- {item.name}
- )
- })}
-
-
-
- this.onChangeTimepublishs(e,index,key)}
- // onChange={ this.onChangeTimepublish }
- disabledTime={disabledDateTime}
- disabledDate={disabledDate}
- />
-
- {key!=0?this.deletegrouppublish(key)}> :""}
- {key===course_groups.length-1? :""}
-
- )
- }):""}
-
-
- {unified_setting===true?
-
-
-
-
-
:""}
- {/*{this.state.course_group_idtypes===true?
请选择分班
:""}*/}
-
- {/*{course_group_publish_timestype===true?
请填写完整
:""}*/}
+
+
+ 发布设置:
+
+
+ 立即发布
+
+
+ 延迟发布
+
+
+ (按照设置的时间定时发布)
+
+
+