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/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 f9d9531f1..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| @@ -1103,6 +1103,7 @@ class ExercisesController < ApplicationController ActiveRecord::Base.transaction do begin can_commit_exercise = false + 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) @@ -1131,7 +1132,7 @@ 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,"提交失败,当前用户不为课堂学生!") @@ -1151,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) #当前试卷用户的试卷状态 @@ -1322,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 @@ -1708,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..83b80bec4 100644 --- a/app/controllers/homework_commons_controller.rb +++ b/app/controllers/homework_commons_controller.rb @@ -159,8 +159,13 @@ 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? + 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 # 分班情况 @@ -1044,6 +1049,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 +1057,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| 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 7781b5792..ddb0f3ce1 100644 --- a/app/controllers/question_banks_controller.rb +++ b/app/controllers/question_banks_controller.rb @@ -91,12 +91,16 @@ class QuestionBanksController < ApplicationController banks = @object_type.classify.constantize.where(id: params[:object_id]) course = current_user.manage_courses.find_by!(id: params[:course_id]) task_ids = [] + homework_type = "" + container_type = "" banks.each do |bank| case @object_type when 'HomeworkBank' # 作业 task = quote_homework_bank bank, course + homework_type = task.homework_type when 'ExerciseBank' - if bank.container_type == 'Exercise' # 试卷 + container_type = bank.container_type + if container_type == 'Exercise' # 试卷 task = quote_exercise_bank bank, course else # 问卷 task = quote_poll_bank bank, course @@ -108,7 +112,23 @@ class QuestionBanksController < ApplicationController end task_ids << task.id if task end - render :json => {task_ids: task_ids, status: 0, message: "发送成功"} + + case @object_type + when 'HomeworkBank' # 作业 + category_id = course.course_modules.find_by(module_type: homework_type == "normal" ? "common_homework" : "group_homework")&.id + when 'ExerciseBank' + if container_type == 'Exercise' # 试卷 + category_id = course.course_modules.find_by(module_type: "exercise")&.id + else # 问卷 + category_id = course.course_modules.find_by(module_type: "poll")&.id + end + when 'GtaskBank' + category_id = course.course_modules.find_by(module_type: "graduation")&.id + when 'GtopicBank' + category_id = course.course_modules.find_by(module_type: "graduation")&.id + end + + render :json => {task_ids: task_ids, category_id: category_id, status: 0, message: "发送成功"} end def destroy 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/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..28bef0a89 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 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_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/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..0bebe8584 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,15 @@ 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 + 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..5455b7561 --- /dev/null +++ b/app/services/create_bind_user_service.rb @@ -0,0 +1,52 @@ +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? + + 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 @@ + + + + + + + + + + + + + <% 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 %> + +
单位名称域名统计链接管理员<%= sort_tag('创建时间', name: 'id', path: admins_laboratories_path) %>操作
+ +<%= 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 %> + +
+
网站域名设置
+ +
+
+
+ https:// +
+ <%= text_field_tag :identifier, @laboratory.identifier, + maxlength: 15, class: 'form-control font-16', + 'onKeyUp': 'value=value.replace(/[^\w\.\-\/]/ig,"").toLowerCase()', + style: 'text-transform:lowercase'%> +
+ <% rails_env = EduSetting.get('rails_env') %> + <%= rails_env && rails_env != 'production' ? ".#{rails_env}.educoder.net" : '.educoder.net' %> +
+
+ <%# if @laboratory.errors && @laboratory.errors.key?(:identifier) %> + + <%# end %> +
+
+ +
+
网站名称设置
+ +
+ <%= text_field_tag :name, setting.name, placeholder: '输入20个字以内的网站名称', maxlength: 20, class: 'form-control col-12 col-md-4' %> +
+
+ +
+
Logo设置
+ +
+
+ <% nav_logo_img = setting.nav_logo_url %> + +
+
网站导航logo
+
格式:PNG、JPG
+
尺寸:高度38px以内,宽等比例缩放
+
+
+ +
+ <% login_logo_img = setting.login_logo_url %> + +
+
登录页面logo
+
格式:PNG、JPG
+
尺寸:高度90px以内,宽等比例缩放
+
+
+ +
+ <% tab_logo_img = setting.tab_logo_url %> +
+ + <%= file_field_tag(:tab_logo, accept: 'image/x-icon', style: 'display: none', value: params[:tab_logo]) %> + +
+
+
浏览器导航栏logo
+
格式:ico
+
尺寸:16*16 32*32 48*48 64*64
+
+
+
+
+ +
+
导航设置
+ +
+ + + + + + + + + + <% (setting.navbar || setting.default_navbar).each do |nav| %> + + + + + + <% end %> + +
导航名称导航链接是否展示
<%= text_field_tag('navbar[][name]', nav['name'], id: nil, class: 'form-control') %><%= text_field_tag('navbar[][link]', nav['link'], id: nil, class: 'form-control') %> + <%= check_box_tag('navbar[][hidden]', 0, !nav['hidden'], id: nil, class: 'font-16') %> +
+
+
+ +
+
底部备案信息设置
+ + +
+ +
+ +
+ <%= 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/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/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..0765e303b --- /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 + json.login_logo_url setting.login_logo_url || default_setting.login_logo_url + json.tab_logo_url setting.tab_logo_url || default_setting.tab_logo_url + + json.navbar setting.navbar || default_setting.navbar + + json.footer setting.footer || default_setting.footer +end \ 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/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/modules/courses/busyWork/CommonWorkSetting.js b/public/react/src/modules/courses/busyWork/CommonWorkSetting.js index 5d9a7dc16..ad25f553f 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkSetting.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkSetting.js @@ -167,7 +167,12 @@ class CommonWorkSetting extends Component{ } on('commonwork_fetch_all', this.fetchAllListener) - } + + + if(this.props.isAdmin()===true){ + this.setState({startEditFlag: true}) + } + } componentWillUnmount() { off('commonwork_fetch_all', this.fetchAllListener) } diff --git a/public/react/src/modules/courses/coursesPublic/HomeworkModal.js b/public/react/src/modules/courses/coursesPublic/HomeworkModal.js index 89e747004..ee19745c9 100644 --- a/public/react/src/modules/courses/coursesPublic/HomeworkModal.js +++ b/public/react/src/modules/courses/coursesPublic/HomeworkModal.js @@ -46,10 +46,16 @@ class HomeworkModal extends Component{ } if(this.props.starttimes!=undefined&&this.props.starttimes!=""){ + if(this.props.starttimesend!=undefined&&this.props.starttimesend!=""){ + this.setState({ + endtime:this.props.starttimesend, + }) + }else { + this.setState({ + endtime:moment(moment(handleDateString(this.props.starttimes)).add(1, 'week')).format("YYYY-MM-DD HH:mm") + }) + } - this.setState({ - endtime:moment(moment(handleDateString(this.props.starttimes)).add(1, 'months')).format("YYYY-MM-DD HH:mm") - }) } } componentDidUpdate=(prevProps)=>{ @@ -69,9 +75,16 @@ class HomeworkModal extends Component{ if(prevProps.starttimes!=this.props.starttimes){ if(this.props.starttimes!=undefined&&this.props.starttimes!=""){ - this.setState({ - endtime:moment(moment(handleDateString(this.props.starttimes)).add(1, 'months')).format("YYYY-MM-DD HH:mm") - }) + if(this.props.starttimesend!=undefined&&this.props.starttimesend!=""){ + this.setState({ + endtime:this.props.starttimesend, + }) + }else{ + this.setState({ + endtime:moment(moment(handleDateString(this.props.starttimes)).add(1, 'week')).format("YYYY-MM-DD HH:mm") + }) + } + } } } diff --git a/public/react/src/modules/courses/coursesPublic/OneSelfOrderModal.js b/public/react/src/modules/courses/coursesPublic/OneSelfOrderModal.js new file mode 100644 index 000000000..d706bf2ee --- /dev/null +++ b/public/react/src/modules/courses/coursesPublic/OneSelfOrderModal.js @@ -0,0 +1,310 @@ +import React,{ Component } from "react"; +import { Modal,Checkbox,DatePicker} from "antd"; +import { handleDateString } from 'educoder'; +import locale from 'antd/lib/date-picker/locale/zh_CN'; +import moment from 'moment'; +const CheckboxGroup = Checkbox.Group; +const dateFormat = 'YYYY-MM-DD HH:mm'; + +function range(start, end) { + const result = []; + for (let i = start; i < end; i++) { + result.push(i); + } + return result; +} +function disabledDateTime() { + return { + // disabledHours: () => range(0, 24).splice(4, 20), + disabledMinutes: () => range(1, 30).concat(range(31, 60)), + // disabledSeconds: () => [55, 56], + }; +} + + +function disabledDate(current) { + return current && current < moment().endOf('day').subtract(1, 'days'); +} + + + +class OneSelfOrderModal extends Component{ + constructor(props){ + super(props); + this.state={ + group_ids:[], + endtime:"" + } + } + componentDidMount() { + + + if(this.props.course_groups!=undefined&&this.props.course_groups.length!=0){ + + let arr=this.props.course_groups.map(item => item.id); + this.shixunhomeworkedit(arr); + } + + if(this.props.starttimes===undefined||this.props.starttimes===""||this.props.starttimes===null){ + this.setState({ + endtime:moment(moment(handleDateString(this.props.staytime)).add(1, 'months')).format("YYYY-MM-DD HH:mm") + }) + }else{ + this.setState({ + endtime:moment(handleDateString(this.props.starttimes)).format("YYYY-MM-DD HH:mm") + }) + } + } + componentDidUpdate=(prevProps)=>{ + // if(prevProps.visible!=this.props.visible){ + // + // if(this.props.course_groups!=undefined){ + // let arr=this.props.course_groups.map(item => item.id); + // this.shixunhomeworkedit(arr); + // } + // } + if(prevProps.course_groups!=this.props.course_groups){ + if(this.props.course_groups!=undefined){ + let arr=this.props.course_groups.map(item => item.id); + this.shixunhomeworkedit(arr); + } + } + if(prevProps.starttimes!=this.props.starttimes){ + + if(this.props.starttimes===undefined||this.props.starttimes===""||this.props.starttimes===null){ + this.setState({ + endtime:moment(moment(handleDateString(this.props.staytime)).add(1, 'months')).format("YYYY-MM-DD HH:mm") + }) + }else{ + this.setState({ + endtime:moment(handleDateString(this.props.starttimes)).format("YYYY-MM-DD HH:mm") + }) + } + } + } + + //勾选实训 + shixunhomeworkedit=(list)=>{ + + this.setState({ + group_ids:list + }) + this.props.getcourse_groupslist && this.props.getcourse_groupslist(list) + } + + onChangeTimeend= (date, dateString) => { + // console.log('startValue',dateString); + + this.setState({ + endtime: date===null?"":handleDateString(dateString), + }) + + } + + propsSaves=(ds,endtime)=>{ + + if(ds.length ===0&&endtime === ""){ + this.props.Saves() + }else{ + if(this.props.typs!="end"){ + if(endtime === ""||endtime===undefined||endtime===null){ + + this.setState({ + endtimetype:true, + endtimetypevalue:"截止时间不能为空" + }) + return + } + + if(moment(endtime,"YYYY-MM-DD HH:mm") <= moment(this.props.starttimes,"YYYY-MM-DD HH:mm")){ + this.setState({ + endtimetype:true, + endtimetypevalue:"必须晚于发布时间" + }) + return + } + } + this.props.Saves(ds,moment(handleDateString(endtime),"YYYY-MM-DD HH:mm").format("YYYY-MM-DD HH:mm")) + } + + + } + + render(){ + let {group_ids,endtime}=this.state; + let {course_groups}=this.props; + + // console.log(this.props.starttimes) + // console.log(endtime) + // console.log(this.props.starttimes) + // console.log(this.state.endtime) + + // console.log(this.props.starttime,this.props.endtime) + // TODO course_groups为空时的处理 + + // let endtimelist=this.props.starttimes===undefined||this.props.starttimes===""?"":moment(handleDateString(endtime)).add(1,'months') + return( +
    + { + this.props.OneSelftype===true?:"" + } + { + 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 ?"":
      + + { + { + course_groups.map((item,key)=>{ + + return( +
      +
    • + + {item.name} + + +
    • + +
      + ) + }) + } +
      } + +
    + } + + + +
    +
    :""} +
    + ) + } +} +export default OneSelfOrderModal; \ No newline at end of file diff --git a/public/react/src/modules/courses/css/Courses.css b/public/react/src/modules/courses/css/Courses.css index 12c4e8d3e..160104920 100644 --- a/public/react/src/modules/courses/css/Courses.css +++ b/public/react/src/modules/courses/css/Courses.css @@ -1701,4 +1701,47 @@ input.ant-input-number-input:focus { /*}*/ .yslinputcourput .ant-form-explain{ padding-left: 0px !important; +} +.wechatloginfont{ + font-size: 14px; + font-family: PingFangSC-Regular,PingFangSC; + font-weight: 400; + color: #555555; + line-height: 20px; + margin-top: 10px; +} + +.wechatdiv{ + margin-top: 40px !important; + width: 800px !important; +} + +.wechatContent{ + padding: 0px 50px; +} + +.wechatpass{ + width: 300px; + height: 46px; +} + +.wechatnewchat{ + width: 62px; + height: 62px; + background: rgba(255,170,170,1); + border-radius: 50%; + text-align: center; + line-height: 62px; + color: #fff; + margin: 0 auto; +} +.wechatweoldchat{ + width: 62px; + height: 62px; + background:rgba(164,211,255,1); + border-radius: 50%; + text-align: center; + line-height: 62px; + color: #fff; + margin: 0 auto; } \ No newline at end of file diff --git a/public/react/src/modules/courses/exercise/ExerciseReviewAndAnswer.js b/public/react/src/modules/courses/exercise/ExerciseReviewAndAnswer.js index a9143176c..75da4f2fe 100644 --- a/public/react/src/modules/courses/exercise/ExerciseReviewAndAnswer.js +++ b/public/react/src/modules/courses/exercise/ExerciseReviewAndAnswer.js @@ -96,8 +96,8 @@ class ExerciseReviewAndAnswer extends Component{ //window.addEventListener('scroll', this.handleScroll); } - remainTime=()=>{ - let { time } = this.state; + remainTime=(time)=>{ + // let { time } = this.state; let h=moment(parseInt(time)*1000).hour()-8; let m=moment(parseInt(time)*1000).minutes(); let s=moment(parseInt(time)*1000).seconds(); @@ -133,15 +133,21 @@ class ExerciseReviewAndAnswer extends Component{ commit_method:2 }).then((result)=>{ if(result){ - this.setState({ - Modalstype:true, - Modalstopval:'答题结束了,系统已自动提交试卷', - modalsBottomval:"不能再修改答题", - ModalCancel:undefined, - ModalSave:this.sureCommit, - Loadtype:true - }) - this.props.showNotification(`${result.data.message}`); + if(result.data.status===0){ + this.setState({ + Modalstype:true, + Modalstopval:'答题结束了,系统已自动提交试卷', + modalsBottomval:"不能再修改答题", + ModalCancel:undefined, + ModalSave:this.sureCommit, + Loadtype:true + }) + this.props.showNotification(`${result.data.message}`); + } + + if(result.data.status===-2){ + this.remainTime(parseInt(result.data.message)) + } } }).catch((error)=>{ console.log(error); @@ -238,7 +244,7 @@ class ExerciseReviewAndAnswer extends Component{ isSpin:false }) if(result.data.exercise.left_time != null){ - this.remainTime(); + this.remainTime(result.data.exercise.left_time); } } }).catch((error)=>{ diff --git a/public/react/src/modules/courses/graduation/tasks/GraduationTaskDetail.js b/public/react/src/modules/courses/graduation/tasks/GraduationTaskDetail.js index b9830d1fc..66e4ccb8d 100644 --- a/public/react/src/modules/courses/graduation/tasks/GraduationTaskDetail.js +++ b/public/react/src/modules/courses/graduation/tasks/GraduationTaskDetail.js @@ -20,89 +20,89 @@ import Modals from '../../../modals/Modals'; //毕设描述 const GraduationTasksquestions= Loadable({ - loader: () => import('./GraduationTaskssettingquestions'), - loading: Loading, + loader: () => import('./GraduationTaskssettingquestions'), + loading: Loading, }) //毕设任务设置 const GraduationTaskssetting=Loadable({ - loader: () => import('./GraduationTaskssetting'), - loading: Loading, + loader: () => import('./GraduationTaskssetting'), + loading: Loading, }) //毕设任务列表 const GraduationTaskslist=Loadable({ - loader: () => import('./GraduationTaskssettinglist'), - loading: Loading, + loader: () => import('./GraduationTaskssettinglist'), + loading: Loading, }) class GraduationTaskDetail extends Component{ - constructor(props){ - super(props); - this.state={ - modalname:undefined, - visible:false, + constructor(props){ + super(props); + this.state={ + modalname:undefined, + visible:false, Topval:undefined, starttime:undefined, starttimes:undefined, typs:undefined, - endtime:undefined, - Cancelname:undefined, - Savesname:undefined, - Cancel:undefined, - Saves:undefined, - Topvalright:undefined, - Botvalleft:undefined, - course_groupslist:undefined, - course_groups:undefined, - questionslist:undefined, - tab:"list", - visibles:undefined, - Modalstype:undefined, - Modalstopval:undefined, - ModalCancel:undefined, - ModalSave:undefined, - acrossVisible:undefined - } - } - componentDidMount(){ - this.getdatas() - } - getdatas=()=>{ - const task_Id = this.props.match.params.task_Id; - let url="/graduation_tasks/"+task_Id+".json"; - - axios.get(url).then((result)=>{ - if(result.status===200){ - this.setState({ - questionslist:result.data - }) - - } - }).catch((error)=>{ - console.log(error) - }) - } - - - // 交叉评阅设置弹框 - openAcross=()=>{ - this.setState({ - acrossVisible:true - }) - } - closeAcross=()=>{ - this.setState({ - acrossVisible:false - }) + endtime:undefined, + Cancelname:undefined, + Savesname:undefined, + Cancel:undefined, + Saves:undefined, + Topvalright:undefined, + Botvalleft:undefined, + course_groupslist:undefined, + course_groups:undefined, + questionslist:undefined, + tab:"list", + visibles:undefined, + Modalstype:undefined, + Modalstopval:undefined, + ModalCancel:undefined, + ModalSave:undefined, + acrossVisible:undefined + } + } + componentDidMount(){ this.getdatas() - } + } + getdatas=()=>{ + const task_Id = this.props.match.params.task_Id; + let url="/graduation_tasks/"+task_Id+".json"; + axios.get(url).then((result)=>{ + if(result.status===200){ + this.setState({ + questionslist:result.data + }) + + } + }).catch((error)=>{ + console.log(error) + }) + } - //返回 - goback=()=>{ - // let courseId=this.props.match.params.coursesId; - // let category_id=this.props.match.params.category_id; - // window.location.href="/courses/"+courseId+"/graduation_tasks/"+category_id; + // 交叉评阅设置弹框 + openAcross=()=>{ + this.setState({ + acrossVisible:true + }) + } + closeAcross=()=>{ + this.setState({ + acrossVisible:false + }) + this.getdatas() + } + + + + //返回 + goback=()=>{ + // let courseId=this.props.match.params.coursesId; + // let category_id=this.props.match.params.category_id; + // window.location.href="/courses/"+courseId+"/graduation_tasks/"+category_id; // let courseId = this.props.match.params.coursesId; // if(courseId===undefined){ // this.props.history.push("/courses"); @@ -112,66 +112,66 @@ class GraduationTaskDetail extends Component{ // this.props.history.goBack() this.props.history.replace(`/courses/${this.state.questionslist.course_id}/graduation_tasks/${this.state.questionslist.graduation_id}`); - } - //立即发布 - publish=()=>{ - let starttime= this.props.getNowFormatDates(1,1); - let endtime=this.props.getNowFormatDates(2,1); - // this.homeworkstart() - this.setState({ - modalname:"立即发布", - visible:true, + } + //立即发布 + publish=()=>{ + let starttime= this.props.getNowFormatDates(1,1); + let endtime=this.props.getNowFormatDates(2,1); + // this.homeworkstart() + this.setState({ + modalname:"立即发布", + visible:true, Topval:"学生将立即收到毕设任务", - // Botvalleft:"点击修改", + // Botvalleft:"点击修改", // Botval:`本操作只对"未发布"的分班有效`, starttime:moment(moment(new Date())).format("YYYY-MM-DD HH:mm") , starttimes:this.props.getNowFormatDates(1), typs:"start", - endtime:endtime, - Cancelname:"暂不发布", - Savesname:"立即发布", - Cancel:this.cancelmodel, - Saves:this.homepublish, - }) - } - // 确定立即发布 - homepublish=(ids,endtime)=>{ - this.cancelmodel(); - let task_Id=this.props.match.params.task_Id; - const cid = this.props.match.params.coursesId; - // let url = `/courses/${cid}/graduation_tasks/publish_task.json`; - - let url="/courses/"+cid+"/graduation_tasks/publish_task.json" - axios.post(url,{ - task_ids:[task_Id], - group_ids: this.state.course_groupslist, + endtime:endtime, + Cancelname:"暂不发布", + Savesname:"立即发布", + Cancel:this.cancelmodel, + Saves:this.homepublish, + }) + } + // 确定立即发布 + homepublish=(ids,endtime)=>{ + this.cancelmodel(); + let task_Id=this.props.match.params.task_Id; + const cid = this.props.match.params.coursesId; + // let url = `/courses/${cid}/graduation_tasks/publish_task.json`; + + let url="/courses/"+cid+"/graduation_tasks/publish_task.json" + axios.post(url,{ + task_ids:[task_Id], + group_ids: this.state.course_groupslist, end_time:endtime, - }).then((response)=>{ - if (response.data.status == 0) { + }).then((response)=>{ + if (response.data.status == 0) { this.getdatas() - this.props.showNotification(response.data.message); - - this.setState({ - // Modalstopval:response.data.message, - // ModalSave:this.cancelmodel, - // Loadtype:true, - course_groupslist:[], - checkAllValue:false - }) - } - }).catch((error)=>{ - - }) - } - - // 刷新 - resetList=()=>{ - this.getdatas(); - this.child && this.child.searchValue(); - } - - // 立即截止 - end=()=>{ + this.props.showNotification(response.data.message); + + this.setState({ + // Modalstopval:response.data.message, + // ModalSave:this.cancelmodel, + // Loadtype:true, + course_groupslist:[], + checkAllValue:false + }) + } + }).catch((error)=>{ + + }) + } + + // 刷新 + resetList=()=>{ + this.getdatas(); + this.child && this.child.searchValue(); + } + + // 立即截止 + end=()=>{ // this.homeworkstart() this.setState({ modalname:"立即截止", @@ -185,74 +185,74 @@ class GraduationTaskDetail extends Component{ Saves:this.coursetaskend, typs:"end", }) - } - coursetaskend=()=>{ - const coursesId = this.props.match.params.coursesId; - const task_Id = this.props.match.params.task_Id; - - let url = `/courses/${coursesId}/graduation_tasks/end_task.json`; - axios.post(url,{ - task_ids:[task_Id], - group_ids: this.state.course_groupslist, - }).then((response)=>{ - if (response.data.status == 0) { - this.props.showNotification(response.data.message); - this.cancelmodel(); - this.getdatas(); - this.child && this.child.reInit(); - } - - }).catch((error)=>{ - - }) - } - // 取消 - cancelmodel=()=>{ - this.setState({ - Modalstype:false, - Loadtype:false, - visible:false, - Modulationtype:false, - Allocationtype:false, - Modalstopval:"", - ModalCancel:"", - ModalSave:"", - }) - } - - getcourse_groupslist=(id)=>{ - this.setState({ - course_groupslist:id - }) - } - - setTab = (tab) =>{ - this.setState({ - tab - }) - } - - // 关联项目 - AssociationItems=()=>{ + } + coursetaskend=()=>{ + const coursesId = this.props.match.params.coursesId; + const task_Id = this.props.match.params.task_Id; + + let url = `/courses/${coursesId}/graduation_tasks/end_task.json`; + axios.post(url,{ + task_ids:[task_Id], + group_ids: this.state.course_groupslist, + }).then((response)=>{ + if (response.data.status == 0) { + this.props.showNotification(response.data.message); + this.cancelmodel(); + this.getdatas(); + this.child && this.child.reInit(); + } + + }).catch((error)=>{ + + }) + } + // 取消 + cancelmodel=()=>{ + this.setState({ + Modalstype:false, + Loadtype:false, + visible:false, + Modulationtype:false, + Allocationtype:false, + Modalstopval:"", + ModalCancel:"", + ModalSave:"", + }) + } + + getcourse_groupslist=(id)=>{ + this.setState({ + course_groupslist:id + }) + } + + setTab = (tab) =>{ + this.setState({ + tab + }) + } + + // 关联项目 + AssociationItems=()=>{ this.setState({ visibles:true }) - } - Cancel=()=>{ + } + Cancel=()=>{ this.setState({ visibles:false }) - } - // 取消关联 - cannelAssociation=()=>{ + } + // 取消关联 + cannelAssociation=()=>{ this.setState({ Modalstype:true, Modalstopval:"确定要取消该项目关联?", ModalCancel:this.cannerassocition, ModalSave:this.savetassociton }) - } - savetassociton=()=>{ + } + savetassociton=()=>{ this.cannerassocition(); let {questionslist}=this.state; let url = "/graduation_tasks/"+questionslist.task_id+"/graduation_works/cancel_relate_project.json"; @@ -266,7 +266,7 @@ class GraduationTaskDetail extends Component{ }) } - cannerassocition=()=>{ + cannerassocition=()=>{ this.setState({ Modalstype:false, Modalstopval:"", @@ -275,10 +275,10 @@ class GraduationTaskDetail extends Component{ loadtype:false, visibles:false }) - } - // 补交附件 - handaccessory=()=>{ - // let {taskslistdata}=this.state; + } + // 补交附件 + handaccessory=()=>{ + // let {taskslistdata}=this.state; // let courseId=this.props.match.params.coursesId; // // let url="/courses/"+courseId+"/graduation_tasks/"+taskslistdata.work_id+"/appraise" @@ -292,129 +292,129 @@ class GraduationTaskDetail extends Component{ this.setState({ avisible:false }) - } - - - bindRef = ref => { this.child = ref } ; - render(){ - - let courseId=this.props.match.params.coursesId; - let category_id=this.props.match.params.category_id; - let task_Id=this.props.match.params.task_Id; - let { - questionslist , - tab , - visibles , - Modalstype, - Modalstopval, - ModalCancel, - ModalSave, - acrossVisible - } = this.state - - const commom = { - setTab:this.setTab, - getdatas:this.getdatas - } - return( -
    - { - questionslist && -
    - this.getcourse_groupslist(id)} - /> - {/*关联项目*/} - {visibles===true? - this.Cancel()} - taskid={ questionslist && questionslist.task_id } - funlist={this.resetList} - /> - :""} - - {this.state.avisible===true?:""} - {/*提示*/} - - - { - acrossVisible && - - } - -

    - {questionslist.course_name} - > - {questionslist.graduation_name} - > - 任务详情 -

    -
    -

    - {questionslist.task_name} -

    - - 返回 -
    -
    -
    - - 任务列表 - 毕设描述 - 设置 - - {/*导出成绩*/} - {/*{this.props.isAdmin()?导出成绩:""}*/} - {/*{this.props.isAdmin()?导出作品附件:""}*/} - - - {this.props.isAdmin()?
  • - 导出 - -
  • :""} - {questionslist.work_status===undefined||questionslist.work_status===null||questionslist.work_status.length===0?"":questionslist.work_status.map((item,key)=>{ - return( - + + {this.props.isAdmin()?
  • + 导出 + +
  • :""} + {questionslist.work_status===undefined||questionslist.work_status===null||questionslist.work_status.length===0?"":questionslist.work_status.map((item,key)=>{ + return( + {item==="提交作品"?提交作品:""} - {item==="补交作品"?补交作品:""} - {item==="修改作品"?修改作品:""} - {item==="查看作品"?查看作品 :""} - {item==="创建项目"?创建项目:""} - {item==="关联项目"?关联项目:""} - {item==="取消关联"?取消关联:""} - {item==="补交附件"?补交附件:""} + {item==="补交作品"?补交作品:""} + {item==="修改作品"?修改作品:""} + {item==="查看作品"?查看作品 :""} + {item==="创建项目"?创建项目:""} + {item==="关联项目"?关联项目:""} + {item==="取消关联"?取消关联:""} + {item==="补交附件"?补交附件:""} - ) - })} - - {/*项目在线质量检测*/} - { this.props.isAdmin() ? questionslist.status===1 ? { this.end()} }>立即截止 : "" : "" } - { this.props.isAdmin() ? questionslist.status===0 ? { this.publish()} }>立即发布 : "" : "" } - { this.props.isAdmin() && questionslist.cross_comment ? 交叉评阅设置 : "" } - { this.props.isAdmin() ? 编辑任务 : "" } -
    -
    - - - () - } - > - - () - } - > - - () - }> - - -
    - } - -
    - ) - } + ) + })} + + {/*项目在线质量检测*/} + { this.props.isAdmin() ? questionslist.status===1 ? { this.end()} }>立即截止 : "" : "" } + { this.props.isAdmin() ? questionslist.status===0 ? { this.publish()} }>立即发布 : "" : "" } + { this.props.isAdmin() && questionslist.cross_comment ? 交叉评阅设置 : "" } + { this.props.isAdmin() ? 编辑任务 : "" } + + + + + () + } + > + + () + } + > + + () + }> + + + + } + + + ) + } } // CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC)) export default (GraduationTaskDetail) ; \ No newline at end of file diff --git a/public/react/src/modules/courses/graduation/tasks/index.js b/public/react/src/modules/courses/graduation/tasks/index.js index 9dac05a36..0660d9c2f 100644 --- a/public/react/src/modules/courses/graduation/tasks/index.js +++ b/public/react/src/modules/courses/graduation/tasks/index.js @@ -473,7 +473,7 @@ class GraduationTasks extends Component{ checkAllValue:false }) this.publishcanner(); - debugger + let {search,page,order} = this.state this.fetchAll(search,page,order); } diff --git a/public/react/src/modules/courses/members/ChangeRolePop.js b/public/react/src/modules/courses/members/ChangeRolePop.js index 896360a40..13130394a 100644 --- a/public/react/src/modules/courses/members/ChangeRolePop.js +++ b/public/react/src/modules/courses/members/ChangeRolePop.js @@ -6,7 +6,7 @@ import axios from 'axios' /** 角色数组, CREATOR: 创建者, PROFESSOR: 教师, ASSISTANT_PROFESSOR: 助教, STUDENT: 学生 */ -function ChangeRolePop({ member_roles = [], record, courseId, onChangeRoleSuccess, showNotification }) { +function ChangeRolePop({ member_roles = [], record, courseId, onChangeRoleSuccess, showNotification, getUserId, fetchUser }) { const [checkBoxRoles, setCheckBoxRoles] = useState(member_roles) useEffect(() => { setCheckBoxRoles(member_roles) @@ -32,7 +32,8 @@ function ChangeRolePop({ member_roles = [], record, courseId, onChangeRoleSucces } const onConfirm = async () => { if (checkBoxRoles && checkBoxRoles.length == 0) { - showNotification('请至少选择一个角色') + showNotification('请至少选择一个角色'); + setCheckBoxRoles(member_roles); return; } const url = `/courses/${courseId}/change_member_role.json` @@ -43,7 +44,12 @@ function ChangeRolePop({ member_roles = [], record, courseId, onChangeRoleSucces if (response.data.status == 0) { showNotification('保存成功') onChangeRoleSuccess() + trigger('updatabanner') + if (fetchUser && record.user_id == getUserId) { + fetchUser() + } + } console.log(response) } diff --git a/public/react/src/modules/courses/members/studentsList.js b/public/react/src/modules/courses/members/studentsList.js index e3859345a..3a5a55b71 100644 --- a/public/react/src/modules/courses/members/studentsList.js +++ b/public/react/src/modules/courses/members/studentsList.js @@ -26,11 +26,11 @@ const buildColumns = (that,isParent) => { let showSorter = isParent==true const courseId = that.props.match.params.coursesId const columns=[{ - title: '序号', + title: '序号1', dataIndex: 'id', key: 'id', align:'center', - width:"10%", + width:"8%", className:"color-grey-6", render: (id, student, index) => { return (that.state.page - 1) * 20 + index + 1 @@ -143,7 +143,7 @@ const buildColumns = (that,isParent) => { columns.push({ title: '操作', key: 'action', - width: '20%', + width: '22%', align:'center', render: (text, record) => { return ( @@ -155,6 +155,8 @@ const buildColumns = (that,isParent) => { member_roles={record.member_roles} onChangeRoleSuccess={that.onChangeRoleSuccess} showNotification={that.props.showNotification} + getUserId={that.props.isUserid} + fetchUser={that.props.fetchUser} > ) @@ -545,6 +547,20 @@ class studentsList extends Component{ addDir = () => { trigger('groupAdd', this.props.coursesids) } + addToDir = async () => { + const courseId = this.props.match.params.coursesId + const url = `/courses/${courseId}/join_course_group.json` + const course_group_id = this.props.match.params.course_group_id + + const response = await axios.post(url, { + course_group_id + }) + if (response && response.data.status == 0) { + this.props.showNotification('加入成功') + this.props.updataleftNavfun() + this.fetchAll() + } + } renameDir = () => { const course_group_id = this.props.match.params.course_group_id trigger('groupRename', { id: parseInt(course_group_id), name: this.state.course_group_name}) @@ -591,6 +607,7 @@ class studentsList extends Component{ render(){ const isAdmin = this.props.isAdmin() + const isStudent = this.props.isStudent() const isSuperAdmin = this.props.isSuperAdmin() const isCourseEnd = this.props.isCourseEnd() let { @@ -702,6 +719,8 @@ class studentsList extends Component{ { // pageType !== TYPE_STUDENTS && !isCourseEnd && isAdmin && isParent && this.addDir()}>添加分班 } + { + isStudent && !isParent && course_group_id != 0 && this.addToDir()}>加入分班 } { isAdmin && !isParent && course_group_id != 0 && this.deleteDir()}>删除分班 } { diff --git a/public/react/src/modules/courses/members/teacherList.js b/public/react/src/modules/courses/members/teacherList.js index 72b667300..fe46e9bf4 100644 --- a/public/react/src/modules/courses/members/teacherList.js +++ b/public/react/src/modules/courses/members/teacherList.js @@ -165,6 +165,9 @@ function buildColumns(that) { member_roles={record.member_roles} onChangeRoleSuccess={that.onChangeRoleSuccess} showNotification={that.props.showNotification} + getUserId={that.props.isUserid} + fetchUser={that.props.fetchUser} + > {/* this.addTeacher()}>添加教师 } { isAdmin && this.addStudent()}>添加学生 } */} - { isAdmin && this.showChangeAdminModal()}>更换管理员} + { isAdminOrCreator && this.showChangeAdminModal()}>更换管理员} } diff --git a/public/react/src/modules/courses/shixunHomework/Listofworksstudentone.js b/public/react/src/modules/courses/shixunHomework/Listofworksstudentone.js index 5eed392d5..926e1b023 100644 --- a/public/react/src/modules/courses/shixunHomework/Listofworksstudentone.js +++ b/public/react/src/modules/courses/shixunHomework/Listofworksstudentone.js @@ -80,6 +80,7 @@ class Listofworksstudentone extends Component { teacherdata: undefined, task_status: [], visibles: false, + starttimesend:undefined, course_group_info: [], styletable: { "display": "none" @@ -2789,6 +2790,7 @@ class Listofworksstudentone extends Component { axios.get(url).then((response) => { if (response.status === 200) { + const dataformat = 'YYYY-MM-DD HH:mm'; let starttime = this.props.getNowFormatDates(1); let endtime = this.props.getNowFormatDates(2); this.setState({ @@ -2801,6 +2803,7 @@ class Listofworksstudentone extends Component { starttime: "发布时间:" + moment(moment(new Date())).format("YYYY-MM-DD HH:mm"), endtime: "截止时间:" + endtime, starttimes:starttime, + starttimesend:response.data.end_time===undefined||response.data.end_time===null||response.data.end_time===""?undefined:response.data.end_time, typs:"start", Cancelname: "暂不发布", Savesname: "立即发布", @@ -2949,6 +2952,7 @@ class Listofworksstudentone extends Component { course_groupyslstwo: undefined, typs:undefined, starttimes:undefined, + starttimesend:undefined, }) } @@ -3170,6 +3174,7 @@ class Listofworksstudentone extends Component { course_groups={this.state.course_groups} getcourse_groupslist={(id) => this.getcourse_groupslist(id)} starttimes={this.state.starttimes} + starttimesend={this.state.starttimesend} typs={this.state.typs} /> { diff --git a/public/react/src/modules/courses/shixunHomework/ShixunHomeworkPage.js b/public/react/src/modules/courses/shixunHomework/ShixunHomeworkPage.js index 9b0353b9e..32dd3a4ee 100644 --- a/public/react/src/modules/courses/shixunHomework/ShixunHomeworkPage.js +++ b/public/react/src/modules/courses/shixunHomework/ShixunHomeworkPage.js @@ -61,9 +61,9 @@ class ShixunHomeworkPage extends Component { } Getdataback = (jobsettingsdata, teacherdata) => { - // console.log("56"); - // console.log(jobsettingsdata); - // console.log(teacherdata); + console.log("ShixunHomeworkPage56"); + console.log(jobsettingsdata); + console.log(teacherdata); this.setState({ jobsettingsdatapage: jobsettingsdata.data.message===undefined?jobsettingsdata:undefined, diff --git a/public/react/src/modules/courses/shixunHomework/ShixunStudentWork.js b/public/react/src/modules/courses/shixunHomework/ShixunStudentWork.js index 3caa63ac3..95de82445 100644 --- a/public/react/src/modules/courses/shixunHomework/ShixunStudentWork.js +++ b/public/react/src/modules/courses/shixunHomework/ShixunStudentWork.js @@ -74,6 +74,7 @@ class ShixunStudentWork extends Component { DownloadType:false, DownloadMessageval:undefined, lunxun:true, + starttimesend:undefined, } } @@ -415,6 +416,7 @@ class ShixunStudentWork extends Component { axios.get(url).then((response) => { if(response.status===200){ + const dataformat = 'YYYY-MM-DD HH:mm'; let starttime= this.props.getNowFormatDates(1); let endtime=this.props.getNowFormatDates(2); this.setState({ @@ -432,6 +434,7 @@ class ShixunStudentWork extends Component { Saves:this.homeworkstartend, course_groups:response.data.course_groups, starttimes:starttime, + starttimesend:response.data.end_time===undefined||response.data.end_time===null||response.data.end_time===""?undefined:response.data.end_time, typs:"start", }) } @@ -754,6 +757,7 @@ class ShixunStudentWork extends Component { course_groups={this.state.course_groups} getcourse_groupslist={(id)=>this.getcourse_groupslist(id)} starttimes={this.state.starttimes} + starttimesend={this.state.starttimesend} typs={this.state.typs} />
    diff --git a/public/react/src/modules/courses/shixunHomework/ShixunhomeWorkItem.js b/public/react/src/modules/courses/shixunHomework/ShixunhomeWorkItem.js index 7aa1d44de..88d143011 100644 --- a/public/react/src/modules/courses/shixunHomework/ShixunhomeWorkItem.js +++ b/public/react/src/modules/courses/shixunHomework/ShixunhomeWorkItem.js @@ -354,8 +354,8 @@ class ShixunhomeWorkItem extends Component{ {/* {discussMessage.author.name} */} { discussMessage.author && {discussMessage.author} } - {discussMessage.commit_count===undefined?"":已开始做题 {discussMessage.commit_count}} - {discussMessage.uncommit_count===undefined?"":未开始做题 {discussMessage.uncommit_count}} + {discussMessage.commit_count===undefined?"":已开始做题 {discussMessage.commit_count}人} + {discussMessage.uncommit_count===undefined?"":未开始做题 {discussMessage.uncommit_count}人} {/*{discussMessage.replies_count} 3 未评*/} { @@ -381,7 +381,7 @@ class ShixunhomeWorkItem extends Component{ { discussMessage && discussMessage.upper_category_name && 22 }> - { {discussMessage.upper_category_name}} + { {discussMessage.upper_category_name}} } diff --git a/public/react/src/modules/courses/shixunHomework/Trainingjobsetting.js b/public/react/src/modules/courses/shixunHomework/Trainingjobsetting.js index 142d1e074..5d2462ec0 100644 --- a/public/react/src/modules/courses/shixunHomework/Trainingjobsetting.js +++ b/public/react/src/modules/courses/shixunHomework/Trainingjobsetting.js @@ -120,7 +120,7 @@ class Trainingjobsetting extends Component { showmodel:false, code_review:false, testscripttiptype:false, - + starttimesend:undefined, end_timebool:false, late_timesbool:false, work_efficiencys:false, @@ -1013,18 +1013,17 @@ class Trainingjobsetting extends Component { //完成效率评分占比 onChangeeffectiveness = (e) => { if( e.target.checked === true){ - this.state.latedeductiontwo=20; this.setState({ completionefficiencyscore: e.target.checked, work_efficiencys:e.target.checked, - latedeductiontwo: 20, + latedeductiontwo: 0, }) //均分比例 if(this.state.proportion==="均分比例"){ - this.Equalproportion(20); + this.Equalproportion(0); }else if(this.state.proportion==="经验值比例"){ - this.Empiricalvalueratio(20); + this.Empiricalvalueratio(0); } @@ -1526,6 +1525,7 @@ class Trainingjobsetting extends Component { axios.get(url).then((response) => { if (response.status === 200) { + const dataformat = 'YYYY-MM-DD HH:mm'; let starttime = this.props.getNowFormatDates(1); let endtime = this.props.getNowFormatDates(2); this.setState({ @@ -1537,7 +1537,8 @@ class Trainingjobsetting extends Component { Botval:`本操作只对"未发布"的分班有效`, starttime: "发布时间:" + moment(moment(new Date())).format("YYYY-MM-DD HH:mm"), starttimes:starttime, - typs:"start", + starttimesend:response.data.end_time===undefined||response.data.end_time===null||response.data.end_time===""?undefined:response.data.end_time, + typs:"start", endtime: "截止时间:" + endtime, Cancelname: "暂不发布", Savesname: "立即发布", @@ -1669,6 +1670,7 @@ class Trainingjobsetting extends Component { addnametab: undefined, typs:undefined, starttimes:undefined, + starttimesend:undefined, }) } cancelBox=()=>{ @@ -1725,15 +1727,15 @@ class Trainingjobsetting extends Component { completionefficiencyscore:true, work_efficiencys:this.state.work_efficiencys, unifiedsetting:this.state.unifiedsetting, - latedeductiontwo:20, + latedeductiontwo:this.state.latedeductiontwo, }); //均分比例 try { if(this.state.proportion==="均分比例"){ - this.Equalproportion(20); + this.Equalproportion(this.state.latedeductiontwo); }else if(this.state.proportion==="经验值比例"){ - this.Empiricalvalueratio(20); + this.Empiricalvalueratio(this.state.latedeductiontwo); } }catch (e) { @@ -1838,19 +1840,19 @@ class Trainingjobsetting extends Component { flagPageEditstwo:releasetime, flagPageEditsthrees:deadline, flagPageEditsfor:endtime, - completionefficiencyscore:true, + completionefficiencyscore:false, work_efficiencys:datas.data.work_efficiency, unifiedsetting:datas.data.unified_setting, - latedeductiontwo:20, + latedeductiontwo:datas.data.eff_score, }); //均分比例 // result.data.shixun_evaluation === 0 ? "均分比例" : result.data.shixun_evaluation === 1 ? "经验值比例" : result.data.shixun_evaluation === 2 ? try { if(datas.data.shixun_evaluation === 0){ - this.Equalproportion(20); + this.Equalproportion(datas.data.eff_score); }else if(datas.data.shixun_evaluation === 1){ - this.Empiricalvalueratio(20); + this.Empiricalvalueratio(datas.data.eff_score); } }catch (e) { @@ -2121,7 +2123,8 @@ class Trainingjobsetting extends Component { course_groups={this.state.course_groups} getcourse_groupslist={(id) => this.getcourse_groupslist(id)} starttimes={this.state.starttimes} - typs={this.state.typs} + starttimesend={this.state.starttimesend} + typs={this.state.typs} /> { if (response.status === 200) { + const dataformat = 'YYYY-MM-DD HH:mm'; let starttime = this.props.getNowFormatDates(1); let endtime = this.props.getNowFormatDates(2); this.setState({ @@ -173,6 +175,7 @@ class Workquestionandanswer extends Component { Saves: this.homeworkstartend, course_groups: response.data.course_groups, starttimes: starttime, + starttimesend:response.data.end_time===undefined||response.data.end_time===null||response.data.end_time===""?undefined:response.data.end_time, typs: "start", }) } @@ -441,6 +444,7 @@ class Workquestionandanswer extends Component { course_groups={this.state.course_groups} getcourse_groupslist={(id) => this.getcourse_groupslist(id)} starttimes={this.state.starttimes} + starttimesend={this.state.starttimesend} typs={this.state.typs} />
    diff --git a/public/react/src/modules/courses/shixunHomework/shixunHomework.js b/public/react/src/modules/courses/shixunHomework/shixunHomework.js index 8e68a7398..cbfdaf72b 100644 --- a/public/react/src/modules/courses/shixunHomework/shixunHomework.js +++ b/public/react/src/modules/courses/shixunHomework/shixunHomework.js @@ -4,6 +4,7 @@ import { WordsBtn,on, off, trigger } from 'educoder'; import {BrowserRouter as Router,Route,Switch,Link} from 'react-router-dom'; import axios from'axios'; import HomeworkModal from "../coursesPublic/HomeworkModal"; +import OneSelfOrderModal from "../coursesPublic/OneSelfOrderModal"; import ShixunModal from "../coursesPublic/ShixunModal"; import PathModal from "../coursesPublic/PathModal"; import NewShixunModel from '../coursesPublic/NewShixunModel'; @@ -227,10 +228,11 @@ class ShixunHomework extends Component{ addnametab:undefined, typs:undefined, starttimes:undefined, + OneSelftype:false }) + this.cancelmodel() + this.homeworkupdatalist(Coursename,page,order); this.props.updataleftNavfun() - this.homeworkupdatalist(Coursename,page,order); - this.cancelmodel() } @@ -272,6 +274,48 @@ class ShixunHomework extends Component{ }); } + + + //立即发布 + homeworkOneSelfstart=()=>{ + let selectnum= this.testonSelect(); + if(selectnum===true){ + this.noSelect(); + return + } + let coursesId=this.props.match.params.coursesId; + let url="/courses/"+coursesId+"/all_course_groups.json"; + + axios.get(url).then((response) => { + + if(response.status===200){ + let starttime= this.props.getNowFormatDates(1); + let endtime=this.props.getNowFormatDates(2); + this.setState({ + modalname:"立即发布", + modaltype:response.data.course_groups===null||response.data.course_groups.length===0?2:1, + OneSelftype:true, + typs:"start", + Topval:"学生将立即收到作业", + // Botvalleft:"暂不发布", + Botval:`本操作只对"未发布"的分班有效`, + starttime:"发布时间:"+moment(moment(new Date())).format("YYYY-MM-DD HH:mm"), + starttimes:starttime, + endtime:"截止时间:"+endtime, + Cancelname:"暂不发布", + Savesname:"立即发布", + Cancel:this.homeworkhide, + Saves:this.homeworkstartend, + course_groups:response.data.course_groups, + }) + } + }).catch((error) => { + console.log(error) + }); + + } + + cancelmodels=()=>{ this.setState({ Modalstype:false, @@ -316,10 +360,12 @@ class ShixunHomework extends Component{ }) } - this.props.showNotification(result.data.message) + + this.homeworkupdatalist(Coursename,page,order); + this.cancelmodel() + this.props.showNotification(result.data.message) this.props.updataleftNavfun() - this.homeworkupdatalist(Coursename,page,order); - this.cancelmodel() + } }).catch((error)=>{ console.log(error); @@ -414,11 +460,11 @@ class ShixunHomework extends Component{ Loadtype:false, course_groups:[] }) - + this.homeworkupdatalist(Coursename,page,order); + this.cancelmodel() this.props.showNotification(response.data.message) this.props.updataleftNavfun() - this.homeworkupdatalist(Coursename,page,order); - this.cancelmodel() + } }) .catch(function (error) { @@ -722,8 +768,8 @@ class ShixunHomework extends Component{ checkedtype:false, antIcon:false }) + this.homeworkupdatalist(Coursename,page,order); this.props.showNotification(response.data.message) - this.homeworkupdatalist(Coursename,page,order); this.props.updataleftNavfun() }else{ this.setState({ @@ -782,6 +828,7 @@ class ShixunHomework extends Component{ this.setState({ Modalstype:false, + OneSelftype:false, Loadtype:false, visible:false, Modalstopval:"", @@ -834,9 +881,10 @@ class ShixunHomework extends Component{ Loadtype:false, checkBoxValues:[] }) + this.homeworkupdatalist(Coursename,page,order); this.props.showNotification(response.data.message) - this.homeworkupdatalist(Coursename,page,order); + } }) .catch(function (error) { @@ -886,9 +934,10 @@ class ShixunHomework extends Component{ checkBoxValues:[], checkAllValue:false }) + this.homeworkupdatalist(Coursename,page,order); this.props.showNotification('已完成') this.props.updataleftNavfun() - this.homeworkupdatalist(Coursename,page,order); + } }) } @@ -1022,7 +1071,7 @@ class ShixunHomework extends Component{ loadtype={this.state.Loadtype} antIcon={this.state.antIcon} />:""} - {/*立即发布*/} + {/*批量立即发布*/} {visible===true?this.getcourse_groupslist(id)} />:""} - + {/*{单个立即发布}*/} + {/*this.getcourse_groupslist(id)}*/} + {/*/>*/} {shixunmodal===true||shixunpath===true?:""} {isRender===true?
    {this.handleDialogClose()}}> @@ -519,14 +573,14 @@ class LoginDialog extends Component {
    -
      + {weixinlogin===true?"":
      • {this.enter(0)}}>登录
      • {/*
      • {this.register(0)}}>快捷登录
      • */} -
      +
    }
    -
    + {weixinlogin===true?"":
    @@ -598,7 +652,26 @@ class LoginDialog extends Component {

    - + {this.state.isphone===true?

    + + ———————— 快速登录 ———————— +

    +

    :""} + + } + {weixinlogin===true?:""} + {weixinlogin===true?

    + this.hideweixinlogin()}>返回账号登录 +

    :""}
    {/*快捷登录*/} diff --git a/public/react/src/modules/login/Otherlogin.js b/public/react/src/modules/login/Otherlogin.js new file mode 100644 index 000000000..6e240ece0 --- /dev/null +++ b/public/react/src/modules/login/Otherlogin.js @@ -0,0 +1,301 @@ +import React, {Component} from "react"; +import { + Form, + Select, + Input, + Button, + Checkbox, + Upload, + Icon, + message, + Modal, + Table, + Divider, + InputNumber, + Tag, + DatePicker, + Radio, + Tooltip, + notification, + Layout, + Spin +} from "antd"; +import axios from 'axios'; +import { getImageUrl } from 'educoder' +import {Link, Switch, Route, Redirect} from 'react-router-dom'; +import '../courses/css/members.css'; +import "../courses/common/formCommon.css" +import '../courses/css/Courses.css'; +import beijintulogontwo from '../../../src/images/login/beijintulogontwo.png'; +import educodernet from '../../../src/images/login/educodernet.png'; +const { Header, Footer, Sider, Content } = Layout; +//educoder登入页面 +var sectionStyle = { + "height": "100%", + "width": "100%", + "min-width": "1000px", +// makesure here is String确保这里是一个字符串,以下是es6写法 + + +}; +var imgback = { + " background-size":"cover", +"background-repeat":"no-repeat", + backgroundImage: `url(${beijintulogontwo})`, +} +var imgmian ={ + width: "100%", + background: `url(${beijintulogontwo})`, + position: "relative", + +} + +var newContainer={ + // background: `url(${beijintulogontwo})`, + backgroundPosition: "center" , + backgroundRepeat: "no-repeat", + backgroundAttachment: "fixed", + backgroundSize: "100% 100%", + height:" 100%", + width:" 100%", + position: "absolute", + top: "0px", + bottom: "0px", + minHeight: "100%", + paddingTop: "40px", + +} +class Otherlogin extends Component { + constructor(props) { + super(props); + this.state={ + login:undefined, + password:undefined, + data:undefined, + logintypes:undefined, + spinnings:true + } + + } + + + componentDidMount() { + + let url = `/users/get_user_info.json` + axios.get(url).then((result)=> { + console.log(result); + if(result){ + this.setState({ + data:result.data, + spinnings:false + }) + } + }).catch((error)=>{ + this.setState({ + spinnings:false + }) + }) + + + } + + + loginInputonChange=(e)=>{ + if(e.target.value===undefined||e.target.value===""||e.target.value===null){ + + }else{ + if(this.state.logintypes==="username"){ + this.setState({ + logintypes:undefined + }) + } + } + + this.setState({ + login:e.target.value, + }) + } + passwordonChange=(e)=>{ + if(e.target.value===undefined||e.target.value===""||e.target.value===null){ + + }else{ + if(this.state.logintypes==="password"){ + this.setState({ + logintypes:undefined + }) + } + } + + this.setState({ + password:e.target.value, + }) + } + postwechatlogin=(type,username,password)=>{ + + if(type===false){ + if(username===undefined||username===""||username===null){ + this.setState({ + logintypes:"username" + }) + return + } + if(password===undefined||password===""||password===null){ + this.setState({ + logintypes:"password" + }) + return + } + } + let url = "/bind_user.json"; + axios.post(url, { + type: 'wechat', + not_bind:type, + username:username, + password:password + }).then((response) => { + if(response.data.status===0){ + window.location.href="/" + } + }).catch((error) => { + console.log(error) + }); + } + render() { + let {data,logintypes,spinnings} = this.state; + + console.log(logintypes) + return ( +
    + + + +
    +
    +
    + +
    + {data===undefined?"":data.image_url===undefined||data.image_url===null||data.image_url===""?"":} +
    +
    + 为了更好的为您服务,请关联一个EduCoder账号 +
    +
    + +
    + +

    + + +

    + 新用户 +
    + + +
    欢迎来到EduCoder,新用户登录EduCoder可以到“账号管理-安全设置”中绑定手机/邮箱,以后可以用绑定的手机/邮箱,设置的密码登录EduCoder了。
    + 立即体验表示您已经同意我们的 服务协议条款 +
    + +
    +
    + +

    + +

    + + +

    + 老用户 +
    + + +
    已有EduCoder账号,可以输入您的账号和密码,将您的微信账号与EduCoder账号进行绑定。
    + + + + + + + {this.state.logintypes==="username"?请填写账号:this.state.logintypes==="password"?请填写密码:""} +
    + +
    +
    + +

    +
    + +
    +
    +
    © 2019 EduCoder湘ICP备17009477号Trustie   &   IntelliDE inside.
    +
    +
    +
    +
    + ) + } + +} + +export default Otherlogin; diff --git a/public/react/src/modules/login/Otherloginstart.js b/public/react/src/modules/login/Otherloginstart.js new file mode 100644 index 000000000..9b38ea2b9 --- /dev/null +++ b/public/react/src/modules/login/Otherloginstart.js @@ -0,0 +1,52 @@ +import React, { Component } from 'react'; + +import { BrowserRouter as Router, Route, Link } from "react-router-dom"; + +import { Spin } from 'antd'; +import axios from 'axios'; +class Otherloginstart extends Component { + + componentDidMount() { + let query=this.props.location.search; + const type = query.split('?code='); + const types = type[1].split('&state='); + let codeurl = `/auth/wechat/callback.json` + axios.get(codeurl,{params:{ + code:types[0] + }}).then((result)=> { + if(result){ + if(result.data.status===0){ + if(result.data.new_user===true){ + window.location.href="/otherlogin" + }else{ + // this.getinfo() + window.location.href="/" + } + } + } + }).catch((error)=>{ + + }) + + } + + render() { + // Loading + return ( +
    + + +
    + ); + } +} + +export default Otherloginstart; \ No newline at end of file diff --git a/public/react/src/modules/login/WeChat.png b/public/react/src/modules/login/WeChat.png new file mode 100644 index 000000000..afadeb36f Binary files /dev/null and b/public/react/src/modules/login/WeChat.png differ diff --git a/public/react/src/modules/login/WeChat@2x.png b/public/react/src/modules/login/WeChat@2x.png new file mode 100644 index 000000000..2cca81b42 Binary files /dev/null and b/public/react/src/modules/login/WeChat@2x.png differ diff --git a/public/react/src/modules/login/qq.png b/public/react/src/modules/login/qq.png new file mode 100644 index 000000000..1f4a09bdc Binary files /dev/null and b/public/react/src/modules/login/qq.png differ diff --git a/public/react/src/modules/login/qq@2x.png b/public/react/src/modules/login/qq@2x.png new file mode 100644 index 000000000..f6937c2ba Binary files /dev/null and b/public/react/src/modules/login/qq@2x.png differ diff --git a/public/react/src/modules/modals/SendTopics.js b/public/react/src/modules/modals/SendTopics.js index 265bcc7d7..94f134f3f 100644 --- a/public/react/src/modules/modals/SendTopics.js +++ b/public/react/src/modules/modals/SendTopics.js @@ -10,7 +10,8 @@ class SendTopics extends Component{ search:null, Radiolist:undefined, showcheck:false, - smallisSpin:false + smallisSpin:false, + yslbanksMenu:undefined } } @@ -18,8 +19,13 @@ class SendTopics extends Component{ componentDidMount(){ - let{search}=this.state; - this.onupdatalist(search) + // console.log("SendTopicssssssssssss"); + // console.log(this.props); + let{search}=this.state; + this.onupdatalist(search) + this.setState({ + yslbanksMenu:this.props.banksMenu, + }) } onupdatalist=(search)=>{ @@ -49,6 +55,8 @@ class SendTopics extends Component{ onChange=(e)=>{ + console.log("SendTopics"); + console.log(e); this.setState({ Radiolist:e.target.value }) @@ -78,9 +86,60 @@ class SendTopics extends Component{ smallisSpin:false }) if(result.data.status===0){ - this.props.showNotification(result.data.message) - this.props.topicscancelmodel() - this.props.updataslist() + this.props.showNotification(result.data.message); + this.props.topicscancelmodel(); + // result.data.task_ids; + + try { + this.props.updataslist() + }catch (e) { + + } +debugger + if(this.props.mysendall===true){ + //详情页面跳过来的 + try { + var rurls=""; + if(this.state.yslbanksMenu.category==="normal"){ + //普通作业 + rurls=`/courses/${this.state.Radiolist}/common_homeworks/${result.data.task_ids}/setting`; + }else if(this.state.yslbanksMenu.category==="group"){ + //分组作业 + rurls=`/courses/${this.state.Radiolist}/group_homeworks/${result.data.task_ids}/setting`; + }else if(this.state.yslbanksMenu.category==="exercise"){ + // 试卷 + rurls=`/courses/${this.state.Radiolist}/exercises/${result.data.task_ids}/student_exercise_list?tab=3`; + }else if(this.state.yslbanksMenu.category==="poll") { + //问卷 + rurls=`/courses/${this.state.Radiolist}/polls/${result.data.task_ids}/detail?tab=3` + } + window.open(rurls,'_blank'); + }catch (e) { + + } + }else{ + //外部多个列表页跳过来的 + debugger + try { + var rurls=""; + if(this.props.category==="normal"){ + //普通作业 + rurls=`/courses/${this.state.Radiolist}/common_homeworks/${result.data.category_id}`; + }else if(this.props.category==="group"){ + //分组作业 + rurls=`/courses/${this.state.Radiolist}/group_homeworks/${result.data.category_id}`; + }else if(this.props.category==="exercise"){ + // 试卷 + rurls=`/courses/${this.state.Radiolist}/exercises/${result.data.category_id}`; + }else if(this.props.category==="poll") { + //问卷 + rurls=`/courses/${this.state.Radiolist}/polls/${result.data.category_id}` + } + window.open(rurls,'_blank'); + }catch (e) { + console.log(e); + } + } }else{ this.props.showNotification(result.data.message) } diff --git a/public/react/src/modules/paths/PathDetail/DetailCards.js b/public/react/src/modules/paths/PathDetail/DetailCards.js index 2c4020d9a..6bc972a03 100644 --- a/public/react/src/modules/paths/PathDetail/DetailCards.js +++ b/public/react/src/modules/paths/PathDetail/DetailCards.js @@ -199,12 +199,13 @@ class DetailCards extends Component{ this.props.showLoginDialog() return } - // if(this.props.checkIfProfileCompleted()===false){ - // this.setState({ - // AccountProfiletype:true - // }) - // return - // } + + if(this.props.checkIfProfileCompleted()===false){ + this.props.showProfileCompleteDialog() + return + } + + // if(this.props.checkIfProfessionalCertification()===false){ // this.props.showProfileCompleteDialog() // return diff --git a/public/react/src/modules/tpm/TPMBanner.js b/public/react/src/modules/tpm/TPMBanner.js index a67038c36..b660001c6 100644 --- a/public/react/src/modules/tpm/TPMBanner.js +++ b/public/react/src/modules/tpm/TPMBanner.js @@ -586,7 +586,7 @@ class TPMBanner extends Component { const antIcon = ; const MyRate = ({ defaultValue, ...rest }) => { let myValue = defaultValue; - console.log(myValue-Math.floor(myValue)) + // console.log(myValue-Math.floor(myValue)) // if (myValue < Math.ceil(myValue)) { // myValue = Math.floor(myValue) + 0.5; // } diff --git a/public/react/src/modules/tpm/TPMFork/TPMForklist.js b/public/react/src/modules/tpm/TPMFork/TPMForklist.js index 8cb5d2378..59d9d23b8 100644 --- a/public/react/src/modules/tpm/TPMFork/TPMForklist.js +++ b/public/react/src/modules/tpm/TPMFork/TPMForklist.js @@ -90,7 +90,7 @@ export default class TPMFork_listComponent extends Component { const MyRate = ({ defaultValue, ...rest }) => { let myValue = defaultValue; - console.log(myValue-Math.floor(myValue)) + // console.log(myValue-Math.floor(myValue)) // if (myValue < Math.ceil(myValue)) { // myValue = Math.floor(myValue) + 0.5; // } diff --git a/public/react/src/modules/tpm/shixuns/ShixunCard.js b/public/react/src/modules/tpm/shixuns/ShixunCard.js index 021185cc8..d08af8bed 100644 --- a/public/react/src/modules/tpm/shixuns/ShixunCard.js +++ b/public/react/src/modules/tpm/shixuns/ShixunCard.js @@ -48,7 +48,7 @@ class ShixunCard extends Component { let {middleshixundata, pagination, typepvisible, pages, totalcount} = this.props; const MyRate = ({ defaultValue, ...rest }) => { let myValue = defaultValue; - console.log(myValue-Math.floor(myValue)) + // console.log(myValue-Math.floor(myValue)) // if (myValue < Math.ceil(myValue)) { // myValue = Math.floor(myValue) + 0.5; // } diff --git a/public/react/src/modules/user/FindPasswordComponent.js b/public/react/src/modules/user/FindPasswordComponent.js index b6e9dda22..e95ea2b7f 100644 --- a/public/react/src/modules/user/FindPasswordComponent.js +++ b/public/react/src/modules/user/FindPasswordComponent.js @@ -594,7 +594,10 @@ class LoginRegisterComponent extends Component { }) } - + gobackshowbool=()=>{ + this.props.history.push("/login"); + this.props.Setshowbool(1) + } render() { const { // 登录 @@ -621,7 +624,7 @@ class LoginRegisterComponent extends Component { // height: 346px; return ( -
    +
    this.StudyMakeMoney()} @@ -801,6 +804,9 @@ class LoginRegisterComponent extends Component { +

    + this.gobackshowbool()}>返回登录注册 +

    diff --git a/public/react/src/modules/user/LoginRegisterComponent.js b/public/react/src/modules/user/LoginRegisterComponent.js index 8b7df2e02..52e0d6869 100644 --- a/public/react/src/modules/user/LoginRegisterComponent.js +++ b/public/react/src/modules/user/LoginRegisterComponent.js @@ -67,7 +67,7 @@ class LoginRegisterComponent extends Component { MyEduCoderModals:false, registered:undefined, Phonenumberisnotcodmms:undefined, - + weixinlogin:false, } } if(props.loginstatus === false){ @@ -105,6 +105,7 @@ class LoginRegisterComponent extends Component { MyEduCoderModals:false, registered:undefined, Phonenumberisnotcodmms:undefined, + weixinlogin:false, } } @@ -129,21 +130,46 @@ class LoginRegisterComponent extends Component { changeType = () => { this.setState({classpass: 'password'}); } + IsPC=()=>{ + var userAgentInfo = navigator.userAgent; + var Agents = ["Android", "iPhone", + "SymbianOS", "Windows Phone", + "iPad", "iPod"]; + var flag = true; + for (var v = 0; v < Agents.length; v++) { + if (userAgentInfo.indexOf(Agents[v]) > 0) { + flag = false; + break; + } + } + return flag; + } componentDidMount = () => { + let flag = this.IsPC(); //true为PC端,false为手机端 + this.setState({ + isphone:flag + }) // console.log("componentDidUpdate"); // console.log(this.props); let pcipns=this.IsPC(); if (this.props.match.url === "/login") { console.log("11111111111111111111111111"); - this.state = { - tab:["0"], - - } + // this.state = { + // tab:["0"], + // + // } + this.setState({ + tab:["0"] + }) } else if (this.props.match.url === "/register") { - this.state = { - tab:["1"], - - } + console.log("11111111111111111111111111"); + // this.state = { + // tab:["1"], + // + // } + this.setState({ + tab:["1"] + }) } this.setState({ pciphone:pcipns, @@ -857,6 +883,18 @@ class LoginRegisterComponent extends Component { registered:"注册成功" }) }; + + openweixinlogin=()=>{ + this.setState({ + weixinlogin:true + }) + } + hideweixinlogin=()=>{ + this.setState({ + weixinlogin:false, + tab:["0"] + }) + } render() { const { // 登录 @@ -881,16 +919,19 @@ class LoginRegisterComponent extends Component { readAgreement, pciphone, Phonenumberisnotcodmms, + weixinlogin } = this.state // height: 346px; if (this.state.seconds === 0) { // window.location.href='http://www.cnblogs.com/a-cat/'; } - console.log(classpass); + return ( -
    +
    - + {weixinlogin===false? 登录 注册 - + :""} - { - parseInt(tab[0])==0 && + { + weixinlogin===false&&parseInt(tab[0])==0 &&