Merge branch 'dev_aliyun' into dev_hjm_a

dev_hjm_a
hjm 5 years ago
commit c9526a57eb

@ -98,3 +98,7 @@ gem 'aasm'
gem 'enumerize'
gem 'diffy'
# oauth2
gem 'omniauth', '~> 1.9.0'
gem 'omniauth-oauth2', '~> 1.6.0'

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

@ -0,0 +1,86 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-laboratory-settings-show-page, body.admins-laboratory-settings-update-page').length > 0) {
var $container = $('.edit-laboratory-setting-container');
var $form = $container.find('.edit_laboratory');
$('.logo-item-left').on("change", 'input[type="file"]', function () {
var $fileInput = $(this);
var file = this.files[0];
var imageType = /image.*/;
if (file && file.type.match(imageType)) {
var reader = new FileReader();
reader.onload = function () {
var $box = $fileInput.parent();
$box.find('img').attr('src', reader.result).css('display', 'block');
$box.addClass('has-img');
};
reader.readAsDataURL(file);
} else {
}
});
createMDEditor('laboratory-footer-editor', { height: 200, placeholder: '请输入备案信息' });
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
errorPlacement:function(error,element){
if(element.parent().hasClass("input-group")){
element.parent().after(error);
}else{
element.after(error)
}
},
rules: {
identifier: {
required: true,
checkSite: true
},
name: {
required: true
}
}
});
$.validator.addMethod("checkSite",function(value,element,params){
var checkSite = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
return this.optional(element)||(checkSite.test(value + '.educoder.com'));
},"域名不合法!");
$form.on('click', '.submit-btn', function(){
$form.find('.submit-btn').attr('disabled', 'disabled');
$form.find('.error').html('');
var valid = $form.valid();
$('input[name="navbar[][name]"]').each(function(_, e){
var $ele = $(e);
if($ele.val() === undefined || $ele.val().length === 0){
$ele.addClass('danger text-danger');
valid = false;
} else {
$ele.removeClass('danger text-danger');
}
});
if(!valid) return;
$.ajax({
method: 'PATCH',
dataType: 'json',
url: $form.attr('action'),
data: new FormData($form[0]),
processData: false,
contentType: false,
success: function(data){
$.notify({ message: '保存成功' });
window.location.reload();
},
error: function(res){
var data = res.responseJSON;
$form.find('.error').html(data.message);
},
complete: function(){
$form.find('.submit-btn').attr('disabled', false);
}
});
})
}
});

@ -0,0 +1,164 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-laboratories-index-page').length > 0) {
var $searchContainer = $('.laboratory-list-form');
var $searchForm = $searchContainer.find('form.search-form');
var $list = $('.laboratory-list-container');
// ============== 新建 ===============
var $modal = $('.modal.admin-create-laboratory-modal');
var $form = $modal.find('form.admin-create-laboratory-form');
var $schoolSelect = $modal.find('.school-select');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
school_id: {
required: true
}
},
messages: {
school_id: {
required: '请选择所属单位'
}
}
});
// modal ready fire
$modal.on('show.bs.modal', function () {
$schoolSelect.select2('val', ' ');
});
// ************** 学校选择 *************
var matcherFunc = function(params, data){
if ($.trim(params.term) === '') {
return data;
}
if (typeof data.text === 'undefined') {
return null;
}
if (data.name && data.name.indexOf(params.term) > -1) {
var modifiedData = $.extend({}, data, true);
return modifiedData;
}
// Return `null` if the term should not be displayed
return null;
};
var defineSchoolSelect = function(schools) {
$schoolSelect.select2({
theme: 'bootstrap4',
placeholder: '请选择单位',
minimumInputLength: 1,
data: schools,
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.name;
},
templateSelection: function(item){
if (item.id) {
$('#school_id').val(item.id);
}
return item.name || item.text;
},
matcher: matcherFunc
});
}
$.ajax({
url: '/api/schools/for_option.json',
dataType: 'json',
type: 'GET',
success: function(data) {
defineSchoolSelect(data.schools);
}
});
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
if ($form.valid()) {
var url = $form.data('url');
$.ajax({
method: 'POST',
dataType: 'json',
url: url,
data: $form.serialize(),
success: function(){
$.notify({ message: '创建成功' });
$modal.modal('hide');
setTimeout(function(){
window.location.reload();
}, 500);
},
error: function(res){
var data = res.responseJSON;
$form.find('.error').html(data.message);
}
});
}
});
// ============= 添加管理员 ==============
var $addMemberModal = $('.admin-add-laboratory-user-modal');
var $addMemberForm = $addMemberModal.find('.admin-add-laboratory-user-form');
var $memberSelect = $addMemberModal.find('.laboratory-user-select');
var $laboratoryIdInput = $addMemberForm.find('input[name="laboratory_id"]')
$addMemberModal.on('show.bs.modal', function(event){
var $link = $(event.relatedTarget);
var laboratoryId = $link.data('laboratory-id');
$laboratoryIdInput.val(laboratoryId);
$memberSelect.select2('val', ' ');
});
$memberSelect.select2({
theme: 'bootstrap4',
placeholder: '请输入要添加的管理员姓名',
multiple: true,
minimumInputLength: 1,
ajax: {
delay: 500,
url: '/admins/users',
dataType: 'json',
data: function(params){
return { name: params.term };
},
processResults: function(data){
return { results: data.users }
}
},
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.real_name;
},
templateSelection: function(item){
if (item.id) {
}
return item.real_name || item.text;
}
});
$addMemberModal.on('click', '.submit-btn', function(){
$addMemberForm.find('.error').html('');
var laboratoryId = $laboratoryIdInput.val();
var memberIds = $memberSelect.val();
if (laboratoryId && memberIds && memberIds.length > 0) {
$.ajax({
method: 'POST',
dataType: 'script',
url: '/admins/laboratories/' + laboratoryId + '/laboratory_user',
data: { user_ids: memberIds }
});
} else {
$addMemberModal.modal('hide');
}
});
}
});

@ -0,0 +1,99 @@
.admins-laboratories-index-page {
.laboratory-list-table {
.member-container {
.laboratory-user {
display: flex;
justify-content: center;
flex-wrap: wrap;
.laboratory-user-item {
display: flex;
align-items: center;
height: 22px;
line-height: 22px;
padding: 2px 5px;
margin: 2px 2px;
border: 1px solid #91D5FF;
background-color: #E6F7FF;
color: #91D5FF;
border-radius: 4px;
}
}
}
}
}
.admins-laboratory-settings-show-page, .admins-laboratory-settings-update-page {
.edit-laboratory-setting-container {
.logo-item {
display: flex;
&-img {
display: block;
width: 80px;
height: 80px;
}
&-upload {
cursor: pointer;
position: absolute;
top: 0;
width: 80px;
height: 80px;
background: #F5F5F5;
border: 1px solid #E5E5E5;
&::before {
content: '';
position: absolute;
top: 27px;
left: 39px;
width: 2px;
height: 26px;
background: #E5E5E5;
}
&::after {
content: '';
position: absolute;
top: 39px;
left: 27px;
width: 26px;
height: 2px;
background: #E5E5E5;
}
}
&-left {
position: relative;
width: 80px;
height: 80px;
&.has-img {
.logo-item-upload {
display: none;
}
&:hover {
.logo-item-upload {
display: block;
background: rgba(145, 145, 145, 0.8);
}
}
}
}
&-right {
display: flex;
flex-direction: column;
justify-content: space-between;
color: #777777;
font-size: 12px;
}
&-title {
color: #23272B;
font-size: 14px;
}
}
}
}

@ -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邮箱类型

@ -0,0 +1,32 @@
class Admins::LaboratoriesController < Admins::BaseController
def index
params[:sort_by] = params[:sort_by].presence || 'id'
params[:sort_direction] = params[:sort_direction].presence || 'desc'
laboratories = Admins::LaboratoryQuery.call(params)
@laboratories = paginate laboratories.preload(:school, :laboratory_users)
end
def create
Admins::CreateLaboratoryService.call(create_params)
render_ok
rescue Admins::CreateLaboratoryService::Error => ex
render_error(ex.message)
end
def destroy
current_laboratory.destroy!
render_delete_success
end
private
def current_laboratory
@_current_laboratory ||= Laboratory.find(params[:id])
end
def create_params
params.permit(:school_id)
end
end

@ -0,0 +1,20 @@
class Admins::LaboratorySettingsController < Admins::BaseController
def show
@laboratory = current_laboratory
end
def update
Admins::SaveLaboratorySettingService.call(current_laboratory, form_params)
render_ok
end
private
def current_laboratory
@_current_laboratory ||= Laboratory.find(params[:laboratory_id])
end
def form_params
params.permit(:identifier, :name, :nav_logo, :login_logo, :tab_logo, :footer, navbar: %i[name link hidden])
end
end

@ -0,0 +1,19 @@
class Admins::LaboratoryUsersController < Admins::BaseController
helper_method :current_laboratory
def create
Admins::AddLaboratoryUserService.call(current_laboratory, params.permit(user_ids: []))
current_laboratory.reload
end
def destroy
@laboratory_user = current_laboratory.laboratory_users.find_by(user_id: params[:user_id])
@laboratory_user.destroy! if @laboratory_user.present?
end
private
def current_laboratory
@_current_laboratory ||= Laboratory.find(params[:laboratory_id])
end
end

@ -7,6 +7,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")

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

@ -0,0 +1,15 @@
module LaboratoryHelper
extend ActiveSupport::Concern
included do
helper_method :default_setting
end
def current_laboratory
@_current_laboratory ||= (Laboratory.find_by_subdomain(request.subdomain) || Laboratory.find(1))
end
def default_setting
@_default_setting ||= LaboratorySetting.find_by(laboratory_id: 1)
end
end

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

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

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

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

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

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

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

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

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

@ -0,0 +1,5 @@
class SettingsController < ApplicationController
def show
@laboratory = current_laboratory
end
end

@ -4,5 +4,4 @@ class Users::UpdatePasswordForm
attr_accessor :password, :old_password
validates :password, presence: true
validates :old_password, presence: true
end

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,26 @@
class Laboratory < ApplicationRecord
belongs_to :school, optional: true
has_many :laboratory_users, dependent: :destroy
has_many :users, through: :laboratory_users, source: :user
has_one :laboratory_setting, dependent: :destroy
validates :identifier, uniqueness: { case_sensitive: false }, allow_nil: true
def site
rails_env = EduSetting.get('rails_env')
suffix = rails_env && rails_env != 'production' ? ".#{rails_env}.educoder.net" : '.educoder.net'
identifier ? "#{identifier}#{suffix}" : ''
end
def self.find_by_subdomain(subdomain)
return if subdomain.blank?
rails_env = EduSetting.get('rails_env')
subdomain = subdomain.slice(0, subdomain.size - rails_env.size - 1) if subdomain.end_with?(rails_env) # winse.dev => winse
find_by_identifier(subdomain)
end
end

@ -0,0 +1,54 @@
class LaboratorySetting < ApplicationRecord
belongs_to :laboratory
serialize :config, JSON
%i[name navbar footer].each do |method_name|
define_method method_name do
config&.[](method_name.to_s)
end
define_method "#{method_name}=" do |value|
self.config ||= {}
config.[]=(method_name.to_s, value)
end
end
def login_logo_url
logo_url('login')
end
def nav_logo_url
logo_url('nav')
end
def tab_logo_url
logo_url('tab')
end
def default_navbar
self.class.default_config[:navbar]
end
private
def logo_url(type)
return nil unless Util::FileManage.exists?(self, type)
Util::FileManage.source_disk_file_url(self, type)
end
def self.default_config
{
name: nil,
navbar: [
{ 'name' => '实践课程', 'link' => '/paths', 'hidden' => false },
{ 'name' => '翻转课堂', 'link' => '/courses', 'hidden' => false },
{ 'name' => '实现项目', 'link' => '/shixuns', 'hidden' => false },
{ 'name' => '在线竞赛', 'link' => '/competitions', 'hidden' => false },
{ 'name' => '教学案例', 'link' => '/moop_cases', 'hidden' => false },
{ 'name' => '交流问答', 'link' => '/forums', 'hidden' => false },
],
footer: nil
}
end
end

@ -0,0 +1,4 @@
class LaboratoryUser < ApplicationRecord
belongs_to :laboratory
belongs_to :user
end

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

@ -0,0 +1,3 @@
class OpenUsers::QQ < OpenUser
end

@ -0,0 +1,3 @@
class OpenUsers::Wechat < OpenUser
end

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

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

@ -0,0 +1,19 @@
class Admins::AddLaboratoryUserService < ApplicationService
attr_reader :laboratory, :params
def initialize(laboratory, params)
@laboratory = laboratory
@params = params
end
def call
columns = %i[]
LaboratoryUser.bulk_insert(*columns) do |worker|
Array.wrap(params[:user_ids]).compact.each do |user_id|
next if laboratory.laboratory_users.exists?(user_id: user_id)
worker.add(laboratory_id: laboratory.id, user_id: user_id)
end
end
end
end

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

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

@ -1,3 +1,11 @@
class ApplicationService
include Callable
Error = Class.new(StandardError)
private
def strip(str)
str.to_s.strip.presence
end
end

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

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

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

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

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

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

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

@ -0,0 +1,28 @@
<div class="modal fade admin-create-laboratory-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">新建云上实验室</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<form class="admin-create-laboratory-form" data-url="<%= admins_laboratories_path %>">
<div class="form-group d-flex">
<label for="school_id" class="col-form-label">选择单位:</label>
<div class="d-flex flex-column-reverse w-75">
<select id="school_id" name="school_id" class="form-control school-select"></select>
</div>
</div>
<div class="error text-danger"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,40 @@
<% school = laboratory.school %>
<td class="text-left"><%= school&.name || 'EduCoder主站' %></td>
<td class="text-left">
<% if laboratory.identifier %>
<%= link_to laboratory.site, "https://#{laboratory.site}", target: '_blank' %>
<% else %>
--
<% end %>
</td>
<td>
<% if school && school.identifier.present? %>
<%= link_to school.identifier.to_s, statistics_college_path(school.identifier), target: '_blank' %>
<% else %>
--
<% end %>
</td>
<td class="member-container">
<div class="laboratory-user">
<% laboratory.users.each do |user| %>
<span class="laboratory-user-item laboratory-user-item-<%= user.id %>">
<%= link_to user.real_name, "/users/#{user.login}", target: '_blank', data: { toggle: 'tooltip', title: '个人主页' } %>
<%= link_to(admins_laboratory_laboratory_user_path(laboratory, user_id: user.id),
method: :delete, remote: true, class: 'ml-1 delete-laboratory-user-action',
data: { confirm: '确认删除吗?' }) do %>
<i class="fa fa-close"></i>
<% end %>
</span>
<% end %>
</div>
</td>
<td><%= laboratory.created_at.strftime('%Y-%m-%d %H:%M') %></td>
<td class="action-container">
<%= link_to '定制', admins_laboratory_laboratory_setting_path(laboratory) %>
<% if school.present? && laboratory.id != 1 %>
<%= javascript_void_link '添加管理员', class: 'action', data: { laboratory_id: laboratory.id, toggle: 'modal', target: '.admin-add-laboratory-user-modal' } %>
<%= delete_link '删除', admins_laboratory_path(laboratory, element: ".laboratory-item-#{laboratory.id}"), class: 'delete-laboratory-action' %>
<% end %>
</td>

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -0,0 +1,4 @@
'zh-CN':
oauth:
wechat:
'40029': '授权已失效,请重新授权'

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

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

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

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

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

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

File diff suppressed because one or more lines are too long

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

@ -134528,6 +134528,256 @@ $(document).on('turbolinks:load', function() {
}
})
;
$(document).on('turbolinks:load', function() {
if ($('body.admins-laboratory-settings-show-page, body.admins-laboratory-settings-update-page').length > 0) {
var $container = $('.edit-laboratory-setting-container');
var $form = $container.find('.edit_laboratory');
$('.logo-item-left').on("change", 'input[type="file"]', function () {
var $fileInput = $(this);
var file = this.files[0];
var imageType = /image.*/;
if (file && file.type.match(imageType)) {
var reader = new FileReader();
reader.onload = function () {
var $box = $fileInput.parent();
$box.find('img').attr('src', reader.result).css('display', 'block');
$box.addClass('has-img');
};
reader.readAsDataURL(file);
} else {
}
});
createMDEditor('laboratory-footer-editor', { height: 200, placeholder: '请输入备案信息' });
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
errorPlacement:function(error,element){
if(element.parent().hasClass("input-group")){
element.parent().after(error);
}else{
element.after(error)
}
},
rules: {
identifier: {
required: true,
checkSite: true
},
name: {
required: true
}
}
});
$.validator.addMethod("checkSite",function(value,element,params){
var checkSite = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
return this.optional(element)||(checkSite.test(value + '.educoder.com'));
},"域名不合法!");
$form.on('click', '.submit-btn', function(){
$form.find('.submit-btn').attr('disabled', 'disabled');
$form.find('.error').html('');
var valid = $form.valid();
$('input[name="navbar[][name]"]').each(function(_, e){
var $ele = $(e);
if($ele.val() === undefined || $ele.val().length === 0){
$ele.addClass('danger text-danger');
valid = false;
} else {
$ele.removeClass('danger text-danger');
}
});
if(!valid) return;
$.ajax({
method: 'PATCH',
dataType: 'json',
url: $form.attr('action'),
data: new FormData($form[0]),
processData: false,
contentType: false,
success: function(data){
$.notify({ message: '保存成功' });
window.location.reload();
},
error: function(res){
var data = res.responseJSON;
$form.find('.error').html(data.message);
},
complete: function(){
$form.find('.submit-btn').attr('disabled', false);
}
});
})
}
});
$(document).on('turbolinks:load', function() {
if ($('body.admins-laboratories-index-page').length > 0) {
var $searchContainer = $('.laboratory-list-form');
var $searchForm = $searchContainer.find('form.search-form');
var $list = $('.laboratory-list-container');
// ============== 新建 ===============
var $modal = $('.modal.admin-create-laboratory-modal');
var $form = $modal.find('form.admin-create-laboratory-form');
var $schoolSelect = $modal.find('.school-select');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
school_id: {
required: true
}
},
messages: {
school_id: {
required: '请选择所属单位'
}
}
});
// modal ready fire
$modal.on('show.bs.modal', function () {
$schoolSelect.select2('val', ' ');
});
// ************** 学校选择 *************
var matcherFunc = function(params, data){
if ($.trim(params.term) === '') {
return data;
}
if (typeof data.text === 'undefined') {
return null;
}
if (data.name && data.name.indexOf(params.term) > -1) {
var modifiedData = $.extend({}, data, true);
return modifiedData;
}
// Return `null` if the term should not be displayed
return null;
};
var defineSchoolSelect = function(schools) {
$schoolSelect.select2({
theme: 'bootstrap4',
placeholder: '请选择单位',
minimumInputLength: 1,
data: schools,
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.name;
},
templateSelection: function(item){
if (item.id) {
$('#school_id').val(item.id);
}
return item.name || item.text;
},
matcher: matcherFunc
});
}
$.ajax({
url: '/api/schools/for_option.json',
dataType: 'json',
type: 'GET',
success: function(data) {
defineSchoolSelect(data.schools);
}
});
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
if ($form.valid()) {
var url = $form.data('url');
$.ajax({
method: 'POST',
dataType: 'json',
url: url,
data: $form.serialize(),
success: function(){
$.notify({ message: '创建成功' });
$modal.modal('hide');
setTimeout(function(){
window.location.reload();
}, 500);
},
error: function(res){
var data = res.responseJSON;
$form.find('.error').html(data.message);
}
});
}
});
// ============= 添加管理员 ==============
var $addMemberModal = $('.admin-add-laboratory-user-modal');
var $addMemberForm = $addMemberModal.find('.admin-add-laboratory-user-form');
var $memberSelect = $addMemberModal.find('.laboratory-user-select');
var $laboratoryIdInput = $addMemberForm.find('input[name="laboratory_id"]')
$addMemberModal.on('show.bs.modal', function(event){
var $link = $(event.relatedTarget);
var laboratoryId = $link.data('laboratory-id');
$laboratoryIdInput.val(laboratoryId);
$memberSelect.select2('val', ' ');
});
$memberSelect.select2({
theme: 'bootstrap4',
placeholder: '请输入要添加的管理员姓名',
multiple: true,
minimumInputLength: 1,
ajax: {
delay: 500,
url: '/admins/users',
dataType: 'json',
data: function(params){
return { name: params.term };
},
processResults: function(data){
return { results: data.users }
}
},
templateResult: function (item) {
if(!item.id || item.id === '') return item.text;
return item.real_name;
},
templateSelection: function(item){
if (item.id) {
}
return item.real_name || item.text;
}
});
$addMemberModal.on('click', '.submit-btn', function(){
$addMemberForm.find('.error').html('');
var laboratoryId = $laboratoryIdInput.val();
var memberIds = $memberSelect.val();
if (laboratoryId && memberIds && memberIds.length > 0) {
$.ajax({
method: 'POST',
dataType: 'script',
url: '/admins/laboratories/' + laboratoryId + '/laboratory_user',
data: { user_ids: memberIds }
});
} else {
$addMemberModal.modal('hide');
}
});
}
});
$(document).on('turbolinks:load', function() {
if ($('body.admins-library-applies-index-page').length > 0) {
var $searchFrom = $('.library-applies-list-form');

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

@ -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 {
<Route
path="/register" component={EducoderLogin}
/>
<Route
path="/otherloginstart" component={Otherloginstart}
/>
<Route
path="/otherlogin" component={Otherlogin}
/>
<Route path="/users/:username"
render={
(props) => {

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

@ -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")
})
}
}
}
}

@ -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(
<div>
{
this.props.OneSelftype===true?<style>
{
`
body {
overflow: hidden !important;
}
`
}
</style>:""
}
{
this.props.OneSelftype===true? <Modal
keyboard={false}
className={"HomeworkModal"}
title={this.props.modalname}
visible={this.props.OneSelftype}
closable={false}
footer={null}
destroyOnClose={true}
>
<div className="task-popup-content">
{ this.props.usingCheckBeforePost ?
<React.Fragment>
<p className="task-popup-text-center font-16">
<span>发布设置均可修改</span>
<span className={"color-blue underline"} onClick={this.props.onToPublishClick}>
点击修改
</span>
</p>
<p className="task-popup-text-center font-16 mt10">
此设置将对所有分班生效
</p>
</React.Fragment> :
<React.Fragment>
<p className="task-popup-text-center font-16">
{this.props.Topval}
<span className={"color-blue underline"}>{this.props.Topvalright}</span>
</p>
<p className="task-popup-text-center font-16 mt10">
{this.props.Botvalleft===undefined?"":<span className={"colorFF6800"}>"{this.props.Botvalleft}"</span>}
{this.props.Botval}
</p>
</React.Fragment> }
{this.props.starttime===undefined||
this.props.starttime===""?""
: <p className="task-popup-text-center font-16 mt20">
<span className={"font-14 mr20 color979797"}>
<span className={"mr10"}>发布时间:</span>
{this.props.starttime}</span>
{this.props.modaltype===undefined||this.props.modaltype===2? <span className={"font-14 color979797"}>
{/*{this.props.endtime}*/}
<span className={"mr10"}>截止时间:</span>
<DatePicker
dropdownClassName="hideDisable"
showTime={{ format: 'HH:mm' }}
disabledTime={disabledDateTime}
disabledDate={disabledDate}
showToday={false}
locale={locale}
format={dateFormat}
placeholder="请选择截止时间"
id={"endTime"}
width={"210px"}
value={endtime===null||endtime===""?"":moment(endtime, dateFormat)}
onChange={this.onChangeTimeend}
className={ this.state.endtimetype===true?"noticeTip":""}
/>
{this.state.endtimetype===true?<div className={"color-red fr mr90 mt5"}>{this.state.endtimetypevalue}</div>:""}
</span>:""}
</p>}
{/* usingCheckBeforePost 为true的时候 全选所有分班 */}
<style>
{
`
.ant-checkbox-wrapper {
margin-top: 0px;
float: left;
}
.upload_select_box li:hover {
background: transparent;
}
.F4FAFF{
background: #F4FAFF;
}
`
}
</style>
{this.props.modaltype===undefined||this.props.modaltype===2
|| this.props.usingCheckBeforePost ?"":<div className="clearfix edu-txt-center lineh-40 F4FAFF">
<li style={{ width: '100%',padding: "0px 10px"}} className={"mb10"}>
<span style={{"float":"left","color":"#05101A"}} className="task-hide color-grey-name ml50">分班名称</span>
<span style={{"float":"right","color":"#05101A"}} className="task-hide color-grey-name mr70">截止时间</span>
</li>
</div>}
{this.props.modaltype===undefined||this.props.modaltype===2
|| this.props.usingCheckBeforePost ?"":<ul className="upload_select_box fl clearfix mb30"
style={{"overflow-y":"auto",padding:"10px 0px"}}
id="search_not_members_list"
>
{ <Checkbox.Group style={{ width: '100%' }} value={group_ids} onChange={this.shixunhomeworkedit}>
{
course_groups.map((item,key)=>{
return(
<div className="clearfix edu-txt-center lineh-40" key={key}>
<li style={{ width: '100%',padding: "0px 10px"}} className={"mb10"}>
<Checkbox
className="task-hide edu-txt-left"
name="shixun_homework[]"
value={item.id}
key={item.id}
>
<span style={{"textAlign":"left","color":"#05101A"}} className="task-hide color-grey-name">{item.name}</span>
</Checkbox>
<DatePicker
dropdownClassName="hideDisable"
showTime={{ format: 'HH:mm' }}
disabledTime={disabledDateTime}
disabledDate={disabledDate}
showToday={false}
locale={locale}
format={dateFormat}
placeholder="请选择截止时间"
id={"endTime"}
width={"210px"}
value={item.end_time===null||item.end_time===""?"":moment(item.end_time, dateFormat)}
onChange={this.onChangeTimeend}
className={ this.state.endtimetype===true?"noticeTip fr":"fr"}
/>
</li>
</div>
)
})
}
</Checkbox.Group>}
</ul>
}
<div className="clearfix mt30 edu-txt-center mb10">
<a className="task-btn color-white mr30" onClick={this.props.Cancel}>{this.props.Cancelname}</a>
<a className="task-btn task-btn-orange" onClick={()=>this.propsSaves(group_ids,this.state.endtime)}>{this.props.Savesname}</a>
</div>
</div>
</Modal>:""}
</div>
)
}
}
export default OneSelfOrderModal;

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

@ -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)=>{

@ -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(
<div className="newMain clearfix">
{
questionslist &&
<div className={"educontent mb20"}>
<HomeworkModal
starttimes={this.state.starttimes}
typs={this.state.typs}
modalname={this.state.modalname}
visible={this.state.visible}
Topval={this.state.Topval}
Topvalright={this.state.Topvalright}
Botvalleft={this.state.Botvalleft}
Botval={this.state.Botval}
starttime={this.state.starttime}
endtime={this.state.endtime}
Cancelname={this.state.Cancelname}
Savesname={this.state.Savesname}
Cancel={this.state.Cancel}
Saves={this.state.Saves}
course_groups={this.state.course_groups}
modaltype={this.state.modaltype}
getcourse_groupslist={(id) => this.getcourse_groupslist(id)}
/>
{/*关联项目*/}
{visibles===true?
<Associationmodel
modalname={"关联项目"}
visible={visibles}
Cancel={()=>this.Cancel()}
taskid={ questionslist && questionslist.task_id }
funlist={this.resetList}
/>
:""}
{this.state.avisible===true?<AccessoryModal
{...this.props}
modalname={"补交附件"}
visible={this.state.avisible}
Cancelname={"取消"}
Savesname={"确认"}
Cancel={this.Cancelvisible}
categoryid={questionslist.work_id}
setupdate={this.resetList}
/>:""}
{/*提示*/}
<Modals
modalsType={Modalstype}
modalsTopval={Modalstopval}
modalCancel={ModalCancel}
modalSave={ModalSave}
closable={false}
footer={null}
destroyOnClose={true}
centered={true}
/>
{
acrossVisible &&
<GraduationAcross
{...this.props}
{...this.state}
task_Id={task_Id}
modalVisible={acrossVisible}
modalCloss={this.closeAcross}
resetFun={this.resetList}
comment_status={ questionslist && questionslist.comment_status }
/>
}
<p className="clearfix mt10">
<a onClick={this.goback} className="color-grey-9 fl">{questionslist.course_name}</a>
<span className="color-grey-9 fl ml3 mr3">&gt;</span>
<Link to={`/courses/${courseId}/graduation_tasks/${category_id}`} className="color-grey-9 fl">{questionslist.graduation_name}</Link>
<span className="color-grey-9 fl ml3 mr3">&gt;</span>
<span className="color-grey-6">任务详情</span>
</p>
<div className="clearfix mt20 mb20 lineh-25 linbox">
<p className=" fl color-black summaryname">
<Link to={`/courses/${courseId}/graduation_tasks/${category_id}`} className="color-grey-3">{questionslist.task_name}</Link>
</p>
<CoursesListType
typelist={questionslist.task_status}
/>
<a className="color-grey-3 fr font-16 ml30 mr20" onClick={this.goback}>返回</a>
</div>
<div className="stud-class-set bor-bottom-greyE">
<div className="clearfix edu-back-white pl30 pr30 graduationTaskMenu">
<Link className={tab && tab == "list" ? "active" : ""} to={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/detail/"+task_Id+"/list"}>任务列表</Link>
<Link className={tab && tab == "questions" ? "active" : ""} to={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/detail/"+task_Id+"/questions"}>毕设描述</Link>
<Link className={tab && tab == "setting" ? "active" : ""} to={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/detail/"+task_Id+"/setting?tab=3"}>设置</Link>
{/*<a className={"fr color-blue font-16"}>导出成绩</a>*/}
{/*{this.props.isAdmin()?<a href={"/api/graduation_tasks/"+task_Id+"/tasks_list.xls"} className={"fr color-blue font-16"}>导出成绩</a>:""}*/}
{/*{this.props.isAdmin()?<a href={"/api/graduation_tasks/"+task_Id+"/tasks_list.zip"} className={"fr color-blue font-16"}>导出作品附件</a>:""}*/}
<style>
{ `
}
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(
<div className="newMain clearfix">
{
questionslist &&
<div className={"educontent mb20"}>
<HomeworkModal
starttimes={this.state.starttimes}
typs={this.state.typs}
modalname={this.state.modalname}
visible={this.state.visible}
Topval={this.state.Topval}
Topvalright={this.state.Topvalright}
Botvalleft={this.state.Botvalleft}
Botval={this.state.Botval}
starttime={this.state.starttime}
endtime={this.state.endtime}
Cancelname={this.state.Cancelname}
Savesname={this.state.Savesname}
Cancel={this.state.Cancel}
Saves={this.state.Saves}
course_groups={this.state.course_groups}
modaltype={this.state.modaltype}
getcourse_groupslist={(id) => this.getcourse_groupslist(id)}
/>
{/*关联项目*/}
{visibles===true?
<Associationmodel
modalname={"关联项目"}
visible={visibles}
Cancel={()=>this.Cancel()}
taskid={ questionslist && questionslist.task_id }
funlist={this.resetList}
/>
:""}
{this.state.avisible===true?<AccessoryModal
{...this.props}
modalname={"补交附件"}
visible={this.state.avisible}
Cancelname={"取消"}
Savesname={"确认"}
Cancel={this.Cancelvisible}
categoryid={questionslist.work_id}
setupdate={this.resetList}
/>:""}
{/*提示*/}
<Modals
modalsType={Modalstype}
modalsTopval={Modalstopval}
modalCancel={ModalCancel}
modalSave={ModalSave}
closable={false}
footer={null}
destroyOnClose={true}
centered={true}
/>
{
acrossVisible &&
<GraduationAcross
{...this.props}
{...this.state}
task_Id={task_Id}
modalVisible={acrossVisible}
modalCloss={this.closeAcross}
resetFun={this.resetList}
comment_status={ questionslist && questionslist.comment_status }
/>
}
<p className="clearfix mt10">
<a onClick={this.goback} className="color-grey-9 fl">{questionslist.course_name}</a>
<span className="color-grey-9 fl ml3 mr3">&gt;</span>
<Link to={`/courses/${courseId}/graduation_tasks/${category_id}`} className="color-grey-9 fl">{questionslist.graduation_name}</Link>
<span className="color-grey-9 fl ml3 mr3">&gt;</span>
<span className="color-grey-6">任务详情</span>
</p>
<div className="clearfix mt20 mb20 lineh-25 linbox">
<p className=" fl color-black summaryname">
<Link to={`/courses/${courseId}/graduation_tasks/${category_id}`} className="color-grey-3">{questionslist.task_name}</Link>
</p>
<CoursesListType
typelist={questionslist.task_status}
/>
<a className="color-grey-3 fr font-16 ml30 mr20" onClick={this.goback}>返回</a>
</div>
<div className="stud-class-set bor-bottom-greyE">
<div className="clearfix edu-back-white pl30 pr30 graduationTaskMenu">
<Link className={tab && tab == "list" ? "active" : ""} to={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/detail/"+task_Id+"/list"}>任务列表</Link>
<Link className={tab && tab == "questions" ? "active" : ""} to={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/detail/"+task_Id+"/questions"}>毕设描述</Link>
<Link className={tab && tab == "setting" ? "active" : ""} to={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/detail/"+task_Id+"/setting?tab=3"}>设置</Link>
{/*<a className={"fr color-blue font-16"}>导出成绩</a>*/}
{/*{this.props.isAdmin()?<a href={"/api/graduation_tasks/"+task_Id+"/tasks_list.xls"} className={"fr color-blue font-16"}>导出成绩</a>:""}*/}
{/*{this.props.isAdmin()?<a href={"/api/graduation_tasks/"+task_Id+"/tasks_list.zip"} className={"fr color-blue font-16"}>导出作品附件</a>:""}*/}
<style>
{ `
.drop_down_menu{
height: 118px;
left:0px;
@ -442,63 +442,63 @@ class GraduationTaskDetail extends Component{
height: 26px;
}
`}
</style>
{this.props.isAdmin()? <li className="li_line drop_down fr color-blue font-16 mt20" style={{"paddingLeft":"0px"}}>
导出<i className="iconfont icon-xiajiantou font-12 ml2"></i>
<ul className="drop_down_menu" style={{"right":"-34px","left":"unset","height":"auto"}}>
<li><a onClick={()=>this.child.confirmysl("/graduation_tasks/"+task_Id+"/tasks_list.xlsx")} className="color-dark">导出成绩</a></li>
<li><a onClick={()=>this.child.confirmysl("/graduation_tasks/"+task_Id+"/tasks_list.zip")} className="color-dark">导出作品附件</a></li>
</ul>
</li>:""}
{questionslist.work_status===undefined||questionslist.work_status===null||questionslist.work_status.length===0?"":questionslist.work_status.map((item,key)=>{
return(
<span key={key} className="fr mt20">
</style>
{this.props.isAdmin()? <li className="li_line drop_down fr color-blue font-16 mt20" style={{"paddingLeft":"0px"}}>
导出<i className="iconfont icon-xiajiantou font-12 ml2"></i>
<ul className="drop_down_menu" style={{"right":"-34px","left":"unset","height":"auto"}}>
<li><a onClick={()=>this.child.confirmysl("/graduation_tasks/"+task_Id+"/tasks_list.xlsx")} className="color-dark">导出成绩</a></li>
<li><a onClick={()=>this.child.confirmysl("/graduation_tasks/"+task_Id+"/tasks_list.zip")} className="color-dark">导出作品附件</a></li>
</ul>
</li>:""}
{questionslist.work_status===undefined||questionslist.work_status===null||questionslist.work_status.length===0?"":questionslist.work_status.map((item,key)=>{
return(
<span key={key} className="fr mt20">
{item==="提交作品"?<a className={"fr color-blue font-16 ml20"} href={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/works/"+task_Id+"/new"}>提交作品</a>:""}
{item==="补交作品"?<a className={"fr color-blue font-16 ml20"} href={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/works/"+task_Id+"/new"}>补交作品</a>:""}
{item==="修改作品"?<a className={"fr color-blue font-16 ml20"} href={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/works"+"/"+ questionslist.work_id + "/edit"}>修改作品</a>:""}
{item==="查看作品"?<a className={"fr color-blue font-16 ml20"} target="_blank" href={"/courses/"+courseId+"/graduation_tasks/"+ questionslist.work_id + "/appraise"}>查看作品</a> :""}
{item==="创建项目"?<a className={"fr color-blue font-16 ml20"} href={'/projects/new'} target="_blank">创建项目</a>:""}
{item==="关联项目"?<a className={"fr color-blue font-16 ml20"} onClick={this.AssociationItems}>关联项目</a>:""}
{item==="取消关联"?<a className={"fr color-blue font-16 ml20"} onClick={this.cannelAssociation}>取消关联</a>:""}
{item==="补交附件"?<a className={"fr color-blue font-16 ml20"} onClick={this.handaccessory}>补交附件</a>:""}
{item==="补交作品"?<a className={"fr color-blue font-16 ml20"} href={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/works/"+task_Id+"/new"}>补交作品</a>:""}
{item==="修改作品"?<a className={"fr color-blue font-16 ml20"} href={"/courses/"+courseId+"/graduation_tasks/"+category_id+"/works"+"/"+ questionslist.work_id + "/edit"}>修改作品</a>:""}
{item==="查看作品"?<a className={"fr color-blue font-16 ml20"} target="_blank" href={"/courses/"+courseId+"/graduation_tasks/"+ questionslist.work_id + "/appraise"}>查看作品</a> :""}
{item==="创建项目"?<a className={"fr color-blue font-16 ml20"} href={'/projects/new'} target="_blank">创建项目</a>:""}
{item==="关联项目"?<a className={"fr color-blue font-16 ml20"} onClick={this.AssociationItems}>关联项目</a>:""}
{item==="取消关联"?<a className={"fr color-blue font-16 ml20"} onClick={this.cannelAssociation}>取消关联</a>:""}
{item==="补交附件"?<a className={"fr color-blue font-16 ml20"} onClick={this.handaccessory}>补交附件</a>:""}
</span>
)
})}
{/*<a className={"fr color-blue font-16"}>项目在线质量检测</a>*/}
{ this.props.isAdmin() ? questionslist.status===1 ? <a className={"fr color-blue font-16 mr20"} onClick={() => { this.end()} }>立即截止</a> : "" : "" }
{ this.props.isAdmin() ? questionslist.status===0 ? <a className={"fr color-blue font-16 mr20"} onClick={() => { this.publish()} }>立即发布</a> : "" : "" }
{ this.props.isAdmin() && questionslist.cross_comment ? <a className={"fr color-blue font-16"} onClick={this.openAcross}>交叉评阅设置</a> : "" }
{ this.props.isAdmin() ? <a className={"fr color-blue font-16"} href={"/courses/"+courseId+"/graduation_tasks/"+task_Id+"/edit"}>编辑任务</a> : "" }
</div>
</div>
<Switch {...this.props}>
<Route exact path="/courses/:coursesId/graduation_tasks/:category_id/detail/:task_Id/list"
render={
(props) => (<GraduationTaskslist {...this.props} {...props} {...this.state} {...commom} triggerRef={this.bindRef} tab={`list`}/>)
}
></Route>
<Route exact path="/courses/:coursesId/graduation_tasks/:category_id/detail/:task_Id/setting"
render={
(props) => (<GraduationTaskssetting {...this.props} {...props} {...this.state} {...commom} triggerRef={this.bindRef} tab={`setting`}/>)
}
></Route>
<Route exact path="/courses/:coursesId/graduation_tasks/:category_id/detail/:task_Id/questions"
render={
(props) => (<GraduationTasksquestions {...this.props} {...props} {...this.state} {...commom} triggerRef={this.bindRef} tab={`questions`}/>)
}></Route>
</Switch>
</div>
}
</div>
)
}
)
})}
{/*<a className={"fr color-blue font-16"}>项目在线质量检测</a>*/}
{ this.props.isAdmin() ? questionslist.status===1 ? <a className={"fr color-blue font-16 mr20"} onClick={() => { this.end()} }>立即截止</a> : "" : "" }
{ this.props.isAdmin() ? questionslist.status===0 ? <a className={"fr color-blue font-16 mr20"} onClick={() => { this.publish()} }>立即发布</a> : "" : "" }
{ this.props.isAdmin() && questionslist.cross_comment ? <a className={"fr color-blue font-16"} onClick={this.openAcross}>交叉评阅设置</a> : "" }
{ this.props.isAdmin() ? <a className={"fr color-blue font-16"} href={"/courses/"+courseId+"/graduation_tasks/"+task_Id+"/edit"}>编辑任务</a> : "" }
</div>
</div>
<Switch {...this.props}>
<Route exact path="/courses/:coursesId/graduation_tasks/:category_id/detail/:task_Id/list"
render={
(props) => (<GraduationTaskslist {...this.props} {...props} {...this.state} {...commom} triggerRef={this.bindRef} tab={`list`}/>)
}
></Route>
<Route exact path="/courses/:coursesId/graduation_tasks/:category_id/detail/:task_Id/setting"
render={
(props) => (<GraduationTaskssetting {...this.props} {...props} {...this.state} {...commom} triggerRef={this.bindRef} tab={`setting`}/>)
}
></Route>
<Route exact path="/courses/:coursesId/graduation_tasks/:category_id/detail/:task_Id/questions"
render={
(props) => (<GraduationTasksquestions {...this.props} {...props} {...this.state} {...commom} triggerRef={this.bindRef} tab={`questions`}/>)
}></Route>
</Switch>
</div>
}
</div>
)
}
}
// CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC))
export default (GraduationTaskDetail) ;

@ -473,7 +473,7 @@ class GraduationTasks extends Component{
checkAllValue:false
})
this.publishcanner();
debugger
let {search,page,order} = this.state
this.fetchAll(search,page,order);
}

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

@ -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}
></ChangeRolePop>
</React.Fragment>
)
@ -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 && <WordsBtn style="blue" className="mr30" onClick={()=>this.addDir()}>添加分班</WordsBtn> }
{
isStudent && !isParent && course_group_id != 0 && <WordsBtn style="blue" className="mr30" onClick={()=>this.addToDir()}>加入分班</WordsBtn> }
{
isAdmin && !isParent && course_group_id != 0 && <WordsBtn style="blue" className="mr30" onClick={()=>this.deleteDir()}>删除分班</WordsBtn> }
{

@ -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}
></ChangeRolePop>
{/* <Popconfirm
placement="bottom"
@ -646,6 +649,7 @@ class studentsList extends Component{
combineArray = this.state.application_list
}
const isAdminOrTeacher = this.props.isAdminOrTeacher()
const isAdminOrCreator = this.props.isAdminOrCreator()
const isSuperAdmin = this.props.isSuperAdmin()
const hasGraduationModule = this.hasGraduationModule()
const coursesId = this.props.match.params.coursesId
@ -682,7 +686,7 @@ class studentsList extends Component{
{/* { isAdmin && <WordsBtn style="blue" className="mr30" onClick={()=>this.addTeacher()}></WordsBtn> }
{ isAdmin && <WordsBtn style="blue" className="mr30" onClick={()=>this.addStudent()}>添加学生</WordsBtn> } */}
{ isAdmin && <WordsBtn style="blue" className="fr" onClick={()=>this.showChangeAdminModal()}>更换管理员</WordsBtn>}
{ isAdminOrCreator && <WordsBtn style="blue" className="fr" onClick={()=>this.showChangeAdminModal()}>更换管理员</WordsBtn>}
</React.Fragment>
}

@ -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}
/>
{

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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save