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

dev_jupyter
杨树明 5 years ago
commit ed235ca509

@ -0,0 +1,124 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-disciplines-index-page').length > 0) {
// ============== 新建 ===============
var $modal = $('.modal.admin-create-discipline-modal');
var $form = $modal.find('form.admin-create-discipline-form');
var $nameInput = $form.find('input[name="name"]');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
name: {
required: true
}
}
});
// modal ready fire
$modal.on('show.bs.modal', function () {
$nameInput.val('');
});
$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);
}
});
}
});
$(".discipline-list-container").on("change", '.discipline-source-form', function () {
var s_id = $(this).attr("data-id");
var s_value = $(this).val();
var s_name = $(this).attr("name");
var json = {};
json[s_name] = s_value;
$.ajax({
url: "/admins/disciplines/" + s_id,
type: "PUT",
dataType:'script',
data: json
});
});
// 导入学生
var $importDisciplineModal = $('.modal.admin-import-discipline-modal');
var $importDisciplineForm = $importDisciplineModal.find('form.admin-import-discipline-form');
$importDisciplineModal.on('show.bs.modal', function(){
resetFileInputFunc($importDisciplineModal.find('.upload-file-input'));
$importDisciplineModal.find('.file-names').html('选择文件');
$importDisciplineModal.find('.upload-file-input').trigger('click');
});
$importDisciplineModal.on('change', '.upload-file-input', function(e){
var file = $(this)[0].files[0];
$importDisciplineModal.find('.file-names').html(file ? file.name : '请选择文件');
});
var importDisciplineFormValid = function(){
if($importDisciplineForm.find('input[name="file"]').val() == undefined || $importDisciplineForm.find('input[name="file"]').val().length == 0){
$importDisciplineForm.find('.error').html('请选择文件');
return false;
}
return true;
};
var buildResultMessage = function(data){
var messageHtml = "<div>导入结果:成功" + data.success + "条,失败"+ data.fail.length + "条</div>";
return messageHtml;
};
$importDisciplineModal.on('click', '.submit-btn', function(){
$importDisciplineForm.find('.error').html('');
if (importDisciplineFormValid()) {
$('body').mLoading({ text: '正在导入...' });
$.ajax({
method: 'POST',
dataType: 'json',
url: '/admins/import_disciplines',
data: new FormData($importDisciplineForm[0]),
processData: false,
contentType: false,
success: function(data){
$('body').mLoading('destroy');
$importDisciplineModal.modal('hide');
showMessageModal(buildResultMessage(data), function(){
window.location.reload();
});
},
error: function(res){
$('body').mLoading('destroy');
var data = res.responseJSON;
$importDisciplineForm.find('.error').html(data.message);
}
});
}
});
}
});

@ -0,0 +1,31 @@
$(document).on('turbolinks:load', function() {
$('.admin-modal-container').on('show.bs.modal', '.modal.admin-edit-discipline-modal', function(){
var $modal = $('.modal.admin-edit-discipline-modal');
var $form = $modal.find('form.admin-edit-discipline-form');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
'discipline[name]': {
required: true,
maxlength: 20
}
}
});
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
var url = $form.attr('action');
if ($form.valid()) {
$.ajax({
method: 'PATCH',
dataType: 'script',
url: url,
data: $form.serialize()
});
}
});
});
});

@ -0,0 +1,31 @@
$(document).on('turbolinks:load', function() {
$('.admin-modal-container').on('show.bs.modal', '.modal.admin-edit-sub-discipline-modal', function(){
var $modal = $('.modal.admin-edit-sub-discipline-modal');
var $form = $modal.find('form.admin-edit-sub-discipline-form');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
'sub_discipline[name]': {
required: true,
maxlength: 20
}
}
});
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
var url = $form.attr('action');
if ($form.valid()) {
$.ajax({
method: 'PATCH',
dataType: 'script',
url: url,
data: $form.serialize()
});
}
});
});
});

@ -0,0 +1,31 @@
$(document).on('turbolinks:load', function() {
$('.admin-modal-container').on('show.bs.modal', '.modal.admin-edit-tag-discipline-modal', function(){
var $modal = $('.modal.admin-edit-tag-discipline-modal');
var $form = $modal.find('form.admin-edit-tag-discipline-form');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
'tag_discipline[name]': {
required: true,
maxlength: 20
}
}
});
$modal.on('click', '.submit-btn', function(){
$form.find('.error').html('');
var url = $form.attr('action');
if ($form.valid()) {
$.ajax({
method: 'PATCH',
dataType: 'script',
url: url,
data: $form.serialize()
});
}
});
});
});

@ -0,0 +1,65 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-sub-disciplines-index-page').length > 0) {
// ============== 新建 ===============
var $modal = $('.modal.admin-create-sub-discipline-modal');
var $form = $modal.find('form.admin-create-sub-discipline-form');
var $nameInput = $form.find('input[name="name"]');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
name: {
required: true
}
}
});
// modal ready fire
$modal.on('show.bs.modal', function () {
$nameInput.val('');
});
$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);
}
});
}
});
$(".sub-discipline-list-container").on("change", '.sub-discipline-source-form', function () {
var s_id = $(this).attr("data-id");
var s_value = $(this).val();
var s_name = $(this).attr("name");
var json = {};
json[s_name] = s_value;
$.ajax({
url: "/admins/sub_disciplines/" + s_id,
type: "PUT",
dataType:'script',
data: json
});
});
}
});

@ -0,0 +1,65 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-tag-disciplines-index-page').length > 0) {
// ============== 新建 ===============
var $modal = $('.modal.admin-create-tag-discipline-modal');
var $form = $modal.find('form.admin-create-tag-discipline-form');
var $nameInput = $form.find('input[name="name"]');
$form.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
name: {
required: true
}
}
});
// modal ready fire
$modal.on('show.bs.modal', function () {
$nameInput.val('');
});
$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);
}
});
}
});
$(".tag-discipline-list-container").on("change", '.tag-discipline-source-form', function () {
var s_id = $(this).attr("data-id");
var s_value = $(this).val();
var s_name = $(this).attr("name");
var json = {};
json[s_name] = s_value;
$.ajax({
url: "/admins/tag_disciplines/" + s_id,
type: "PUT",
dataType:'script',
data: json
});
});
}
});

@ -0,0 +1,49 @@
class Admins::DisciplinesController < Admins::BaseController
def index
@disciplines = Discipline.all
end
def create
name = params[:name].to_s.strip
return render_error('名称重复') if Discipline.where(name: name).exists?
Discipline.create!(name: name)
render_ok
end
def edit
@discipline = current_discipline
end
def update
if params[:discipline] && params[:discipline][:name].present?
name = params[:discipline][:name].to_s.strip
current_discipline.update_attributes!(name: name)
else
ActiveRecord::Base.transaction do
current_discipline.update_attributes!(setting_params)
current_discipline.sub_disciplines.each do |sub|
sub.tag_disciplines.each do |tag|
tag.update_attributes!(setting_params)
end
sub.update_attributes!(setting_params)
end
end
end
@disciplines = Discipline.all
end
def destroy
@discipline_id = params[:id]
current_discipline.destroy!
end
private
def current_discipline
@_current_discipline = Discipline.find params[:id]
end
def setting_params
params.permit(:shixun, :subject, :question)
end
end

@ -0,0 +1,10 @@
class Admins::ImportDisciplinesController < Admins::BaseController
def create
return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile)
result = Admins::ImportDisciplineService.call(params[:file].to_io)
render_ok(result)
rescue Admins::ImportDisciplineService::Error => ex
render_error(ex)
end
end

@ -0,0 +1,52 @@
class Admins::SubDisciplinesController < Admins::BaseController
def index
@discipline = current_discipline
@sub_disciplines = current_discipline.sub_disciplines
end
def create
name = params[:name].to_s.strip
return render_error('名称重复') if current_discipline.sub_disciplines.where(name: name).exists?
SubDiscipline.create!(name: name, discipline_id: current_discipline.id)
render_ok
end
def edit
@sub_discipline = current_sub_discipline
end
def update
if params[:sub_discipline] && params[:sub_discipline][:name].present?
name = params[:sub_discipline][:name].to_s.strip
current_sub_discipline.update_attributes!(name: name)
else
ActiveRecord::Base.transaction do
current_sub_discipline.update_attributes!(setting_params)
current_sub_discipline.tag_disciplines.each do |tag|
tag.update_attributes!(setting_params)
end
end
end
@sub_disciplines = current_sub_discipline.discipline&.sub_disciplines
end
def destroy
@sub_discipline_id = params[:id]
current_sub_discipline.destroy!
end
private
def current_sub_discipline
@_current_sub_discipline = SubDiscipline.find params[:id]
end
def current_discipline
@_current_discipline = Discipline.find params[:discipline_id]
end
def setting_params
params.permit(:shixun, :subject, :question)
end
end

@ -0,0 +1,47 @@
class Admins::TagDisciplinesController < Admins::BaseController
def index
@sub_discipline = current_sub_discipline
@tag_disciplines = current_sub_discipline.tag_disciplines
end
def create
name = params[:name].to_s.strip
return render_error('名称重复') if current_sub_discipline.tag_disciplines.where(name: name).exists?
TagDiscipline.create!(name: name, sub_discipline_id: current_sub_discipline.id)
render_ok
end
def edit
@tag_discipline = current_tag_discipline
end
def update
if params[:tag_discipline] && params[:tag_discipline][:name].present?
name = params[:tag_discipline][:name].to_s.strip
current_tag_discipline.update_attributes!(name: name)
else
current_tag_discipline.update_attributes!(setting_params)
end
@tag_disciplines = current_tag_discipline.sub_discipline&.tag_disciplines
end
def destroy
@tag_discipline_id = params[:id]
current_tag_discipline.destroy!
end
private
def current_sub_discipline
@_current_sub_discipline = SubDiscipline.find params[:sub_discipline_id]
end
def current_tag_discipline
@_current_tag_discipline = TagDiscipline.find params[:id]
end
def setting_params
params.permit(:shixun, :subject, :question)
end
end

@ -0,0 +1,6 @@
class DisciplinesController < ApplicationController
def index
end
end

@ -0,0 +1,77 @@
class ExaminationBanksController < ApplicationController
include PaginateHelper
before_action :require_login
before_action :find_exam, except: [:index, :create]
before_action :edit_auth, only: [:update, :destroy, :set_public]
def index
exams = ExaminationBankQuery.call(params)
@exams_count = exams.size
@exams = paginate exams.includes(:user, :examination_items)
end
def show
end
def create
ActiveRecord::Base.transaction do
exam = ExaminationBank.new(user: current_user)
# 保存试卷基础信息
exam = ExaminationBanks::SaveExaminationBankService.call(exam, form_params)
# 将试题篮中的试题发送到试卷,试卷的题目与试题独立
current_user.item_baskets.includes(:item_bank).each do |basket|
item = basket.item_bank
if item.present?
new_item = ExaminationItem.new(examination_bank: exam, item_bank: item, name: item.name, item_type: item.item_type,
difficulty: item.difficulty, score: basket.score, position: basket.position)
if new_item.save!
item.increment!(:quotes)
if item.item_choices.present?
item.item_choices.each do |choice|
new_item.examination_item_choices << ExaminationItemChoice.new(choice_text: choice.choice_text, is_answer: choice.is_answer)
end
end
end
end
end
current_user.item_baskets.destroy_all
end
render_ok
end
def edit
end
def update
end
def destroy
@exam.destroy!
render_ok
end
def set_public
tip_exception(-1, "该试卷已公开") if @exam.public?
@exam.update_attributes!(public: 1)
render_ok
end
private
def form_params
params.permit(:discipline_id, :sub_discipline_id, :difficulty, :name, :duration, tag_discipline_id: [])
end
def find_exam
@exam = ExaminationBank.find_by!(id: params[:id])
end
def edit_auth
current_user.admin_or_business? || @exam.user == current_user
end
end

@ -5,6 +5,7 @@ class HackUserLastestCodesController < ApplicationController
before_action :update_user_hack_status, only: [:code_debug, :code_submit] before_action :update_user_hack_status, only: [:code_debug, :code_submit]
before_action :require_auth_identity, only: [:add_notes] before_action :require_auth_identity, only: [:add_notes]
before_action :require_manager_identity, only: [:show, :update_code, :restore_initial_code, :sync_code] before_action :require_manager_identity, only: [:show, :update_code, :restore_initial_code, :sync_code]
skip_before_action :check_sign, only: [:listen_result]
def show def show
@my_hack.update_attribute(:submit_status, 0) if @my_hack.submit_status == 1 @my_hack.update_attribute(:submit_status, 0) if @my_hack.submit_status == 1

@ -43,14 +43,21 @@ class HacksController < ApplicationController
def create def create
begin begin
logger.info("##########{hack_params}") logger.info("##########{hack_params}")
tip_exception("一次只能增加50个测试集") if hack_sets_params.size > 50
tip_exception("一次只能增加50个知识点") if params[:tags].size > 50
hack = Hack.new(hack_params) hack = Hack.new(hack_params)
hack.user_id = current_user.id hack.user_id = current_user.id
hack.identifier = generate_identifier Hack, 8 hack.identifier = generate_identifier Hack, 8
tag_params =
params[:tags].each do |tag|
{tag_discipline_id: tag}
end
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
hack.save! hack.save!
# 创建测试集与代码 # 创建测试集与代码
hack.hack_sets.create!(hack_sets_params) hack.hack_sets.create!(hack_sets_params)
# 新建知识点 # 新建知识点
hack.tag_discipline_containers.create!(tag_params) if tag_params
hack_codes = hack.hack_codes.new(hack_code_params) hack_codes = hack.hack_codes.new(hack_code_params)
hack_codes.modify_time = Time.now hack_codes.modify_time = Time.now
hack_codes.save! hack_codes.save!
@ -64,6 +71,13 @@ class HacksController < ApplicationController
def update def update
begin begin
# 知识点
tag_discipline_ids = @hack.tag_discipline_containers.pluck(:tag_discipline_id)
new_tag_ids = params[:tags].to_a - tag_discipline_ids
tag_params =
new_tag_ids.each do |tag|
{tag_discipline_id: tag}
end
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
@hack.update_attributes!(hack_params) @hack.update_attributes!(hack_params)
set_ids = @hack.hack_sets.pluck(:id) set_ids = @hack.hack_sets.pluck(:id)
@ -74,6 +88,8 @@ class HacksController < ApplicationController
# 更新代码 # 更新代码
code_params = params[:hack_codes][:code] != @hack.code ? hack_code_params.merge(modify_time: Time.now) : hack_code_params code_params = params[:hack_codes][:code] != @hack.code ? hack_code_params.merge(modify_time: Time.now) : hack_code_params
@hack.hack_codes.first.update_attributes!(code_params) @hack.hack_codes.first.update_attributes!(code_params)
@hack.tag_discipline_containers.create!(tag_params) if tag_params
@hack.tag_discipline_containers.where.not(tag_discipline_id: params[:tags]).destroy_all
end end
render_ok render_ok
rescue Exception => e rescue Exception => e
@ -127,9 +143,11 @@ class HacksController < ApplicationController
@hacks = hacks.includes(:hack_sets).page(page).per(limit) @hacks = hacks.includes(:hack_sets).page(page).per(limit)
end end
def edit;end def edit;
end
def new;end def new;
end
def destroy def destroy
begin begin
@ -148,6 +166,7 @@ class HacksController < ApplicationController
end end
private private
# 实名认证老师,管理员与运营人员权限 # 实名认证老师,管理员与运营人员权限
def require_teacher_identity def require_teacher_identity
unless current_user.certification_teacher? || admin_or_business? unless current_user.certification_teacher? || admin_or_business?
@ -167,7 +186,7 @@ class HacksController < ApplicationController
end end
def hack_params def hack_params
params.require(:hack).permit(:name, :description, :difficult, :category, :open_or_not, :time_limit, :score) params.require(:hack).permit(:name, :description, :difficult, :category, :open_or_not, :time_limit, :score, :sub_discipline_id)
end end
def hack_sets_params def hack_sets_params

@ -1,14 +1,58 @@
class LibrariesController < ApplicationController class ItemBanksController < ApplicationController
include PaginateHelper include PaginateHelper
before_action :require_login
before_action :find_item, except: [:index, :create]
before_action :edit_auth, only: [:update, :destroy, :set_public]
def index def index
default_sort('updated_at', 'desc') items = ItemBankQuery.call(params)
@items_count = items.size
@items = ItemBankQuery.call(params) @items = paginate items.includes(:item_analysis, :user)
@items = paginate courses.includes(:school, :students, :attachments, :homework_commons, teacher: :user_extension) @item_basket_ids = current_user.item_baskets.pluck(:item_bank_id)
end end
def create def create
item = ItemBank.new(user: current_user)
ItemBanks::SaveItemService.call(item, form_params)
render_ok
rescue ApplicationService::Error => ex
render_error(ex.message)
end
def edit
end
def update
ItemBanks::SaveItemService.call(@item, form_params)
render_ok
rescue ApplicationService::Error => ex
render_error(ex.message)
end
def destroy
@item.destroy!
render_ok
end
def set_public
tip_exception(-1, "该试题已公开") if @item.public?
@item.update_attributes!(public: 1)
render_ok
end end
private
def find_item
@item = ItemBank.find_by!(id: params[:id])
end
def edit_auth
current_user.admin_or_business? || @item.user == current_user
end
def form_params
params.permit(:discipline_id, :sub_discipline_id, :item_type, :difficulty, :name, :analysis, tag_discipline_id: [], choices: %i[choice_text is_answer])
end
end end

@ -0,0 +1,90 @@
class ItemBasketsController < ApplicationController
before_action :require_login
before_action :validate_score, only: [:set_score, :batch_set_score]
helper_method :current_basket
def index
@item_baskets = current_user.item_baskets
@single_questions = @item_baskets.where(item_type: "SINGLE")
@multiple_questions = @item_baskets.where(item_type: "MULTIPLE")
@judgement_questions = @item_baskets.where(item_type: "JUDGMENT")
@program_questions = @item_baskets.where(item_type: "PROGRAM")
end
def basket_list
@single_questions_count = current_user.item_baskets.where(item_type: "SINGLE").count
@multiple_questions_count = current_user.item_baskets.where(item_type: "MULTIPLE").count
@judgement_questions_count = current_user.item_baskets.where(item_type: "JUDGMENT").count
@completion_questions_count = current_user.item_baskets.where(item_type: "COMPLETION").count
@subjective_questions_count = current_user.item_baskets.where(item_type: "SUBJECTIVE").count
@practical_questions_count = current_user.item_baskets.where(item_type: "PRACTICAL").count
@program_questions_count = current_user.item_baskets.where(item_type: "PROGRAM").count
end
def create
ItemBaskets::SaveItemBasketService.call(current_user, create_params)
render_ok
rescue ApplicationService::Error => ex
render_error(ex.message)
end
def destroy
item = current_user.item_baskets.find_by!(item_bank_id: params[:id])
ActiveRecord::Base.transaction do
current_user.item_baskets.where(item_type: item.item_type).where("position > #{item.position}").update_all("position = position -1")
item.destroy!
end
render_ok
end
def delete_item_type
baskets = ItemBasket.where(item_type: params[:item_type])
baskets.destroy_all
render_ok
end
def set_score
current_basket.update_attributes!(score: params[:score])
@questions_score = current_user.item_baskets.where(item_type: current_basket.item_type).pluck(:score).sum
@all_score = current_user.item_baskets.pluck(:score).sum
end
def batch_set_score
current_user.item_baskets.where(item_type: params[:item_type]).update_all(score: params[:score])
@questions_score = current_user.item_baskets.where(item_type: params[:item_type]).pluck(:score).sum
@all_score = current_user.item_baskets.pluck(:score).sum
end
def adjust_position
same_items = current_user.item_baskets.where(item_type: current_basket.item_type)
max_position = same_items.size
tip_exception("position超出范围") unless params[:position].present? && params[:position].to_i <= max_position && params[:position].to_i >= 1
ActiveRecord::Base.transaction do
if params[:position].to_i > current_basket.position
same_items.where("position > #{current_basket.position} and position <= #{params[:position].to_i}").update_all("position=position-1")
current_basket.update_attributes!(position: params[:position])
elsif params[:position].to_i < current_basket.position
same_items.where("position < #{current_basket.position} and position >= #{params[:position].to_i}").update_all("position=position+1")
current_basket.update_attributes!(position: params[:position])
else
return normal_status(-1, "排序无变化")
end
end
render_ok
end
private
def create_params
params.permit(item_ids: [])
end
def current_basket
@_current_basket = current_user.item_baskets.find_by!(id: params[:id])
end
def validate_score
tip_exception("分值不能为空") unless params[:score].present?
tip_exception("分值需大于0") unless params[:score].to_f > 0
end
end

@ -0,0 +1,12 @@
class ExaminationBanks::SaveExamForm
include ActiveModel::Model
attr_accessor :discipline_id, :sub_discipline_id, :difficulty, :name, :duration, :tag_discipline_id
validates :discipline_id, presence: true
validates :sub_discipline_id, presence: true
validates :difficulty, presence: true, inclusion: {in: 1..3}, numericality: { only_integer: true }
validates :name, presence: true, length: { maximum: 60 }
validates :duration, numericality: { only_integer: true, greater_than: 0 }
end

@ -0,0 +1,33 @@
class ItemBanks::SaveItemForm
include ActiveModel::Model
attr_accessor :discipline_id, :sub_discipline_id, :item_type, :difficulty, :name, :analysis, :tag_discipline_id, :choices
validates :discipline_id, presence: true
validates :sub_discipline_id, presence: true
validates :item_type, presence: true, inclusion: {in: %W(SINGLE MULTIPLE JUDGMENT COMPLETION SUBJECTIVE PRACTICAL PROGRAM)}
validates :difficulty, presence: true, inclusion: {in: 1..3}, numericality: { only_integer: true }
validates :name, presence: true, length: { maximum: 1000 }
validates :analysis, length: { maximum: 1000 }
def validate!
super
return unless errors.blank?
choices.each { |item| SubForm.new(item).validate! } if %W(SINGLE MULTIPLE JUDGMENT).include?(item_type)
return unless errors.blank?
if [0, 2].include?(item_type) && choices.pluck(:is_answer).select{|item| item == 1}.length > 1
raise("正确答案只能有一个")
elsif item_type == 1 && choices.pluck(:is_answer).select{|item| item == 1}.length <= 1
raise("多选题至少有两个正确答案")
end
end
class SubForm
include ActiveModel::Model
attr_accessor :choice_text, :is_answer
validates :choice_text, presence: true, length: { maximum: 100 }
validates :is_answer, presence: true, inclusion: {in: 0..1}, numericality: { only_integer: true }
end
end

@ -0,0 +1,16 @@
class Admins::ImportDisciplineExcel < BaseImportXlsx
DisciplineData = Struct.new(:discipline_name, :sub_discipline_name)
def read_each(&block)
sheet.each_row_streaming(pad_cells: true, offset: 2) do |row|
data = row.map(&method(:cell_value))[1..2]
block.call DisciplineData.new(*data)
end
end
private
def cell_value(obj)
obj&.cell_value
end
end

@ -0,0 +1,7 @@
class Discipline < ApplicationRecord
has_many :sub_disciplines, dependent: :destroy
has_many :shixun_sub_disciplines, -> { where("shixun = 1") }, class_name: "SubDiscipline"
has_many :subject_sub_disciplines, -> { where("subject = 1") }, class_name: "SubDiscipline"
has_many :question_sub_disciplines, -> { where("question = 1") }, class_name: "SubDiscipline"
end

@ -0,0 +1,17 @@
class ExaminationBank < ApplicationRecord
belongs_to :user
belongs_to :sub_discipline
has_many :tag_discipline_containers, as: :container, dependent: :destroy
has_many :tag_disciplines, through: :tag_discipline_containers
has_many :examination_items, dependent: :destroy
def question_count
examination_items.size
end
def total_score
examination_items.pluck(:score).sum
end
end

@ -0,0 +1,6 @@
class ExaminationItem < ApplicationRecord
belongs_to :examination_bank, touch: true
belongs_to :item_bank, optional: true
has_many :examination_item_choices, dependent: :destroy
end

@ -0,0 +1,3 @@
class ExaminationItemChoice < ApplicationRecord
belongs_to :examination_item
end

@ -15,8 +15,11 @@ class Hack < ApplicationRecord
has_many :praise_treads, as: :praise_tread_object, dependent: :destroy has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
# 消息 # 消息
has_many :tidings, as: :container has_many :tidings, as: :container
# 知识点
has_many :tag_discipline_containers, dependent: :destroy
belongs_to :user belongs_to :user
belongs_to :sub_discipline
scope :published, -> { where(status: 1) } scope :published, -> { where(status: 1) }
scope :unpublish, -> { where(status: 0) } scope :unpublish, -> { where(status: 0) }

@ -1,11 +1,18 @@
class ItemBank < ApplicationRecord class ItemBank < ApplicationRecord
# difficulty: 1 简单 2 适中 3 困难 # difficulty: 1 简单 2 适中 3 困难
# item_type: 0 单选 1 多选 2 判断 3 填空 4 简答 5 实训 6 编程
enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 } enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 }
# item_type: 0 单选 1 多选 2 判断 3 填空 4 简答 5 实训 6 编程
belongs_to :user belongs_to :user
belongs_to :sub_discipline
has_one :item_analysis, dependent: :destroy has_one :item_analysis, dependent: :destroy
has_many :item_choices, dependent: :destroy has_many :item_choices, dependent: :destroy
has_many :item_baskets, dependent: :destroy has_many :item_baskets, dependent: :destroy
has_many :tag_discipline_containers, as: :container, dependent: :destroy
has_many :tag_disciplines, through: :tag_discipline_containers
def analysis
item_analysis&.analysis
end
end end

@ -1,4 +1,14 @@
class ItemBasket < ApplicationRecord class ItemBasket < ApplicationRecord
enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 }
belongs_to :item_bank belongs_to :item_bank
belongs_to :user belongs_to :user
def all_score
User.current.item_baskets.map(&:score).sum
end
def question_count
User.current.item_baskets.size
end
end end

@ -0,0 +1,9 @@
class SubDiscipline < ApplicationRecord
belongs_to :discipline
has_many :tag_disciplines, dependent: :destroy
has_one :hack
has_many :shixun_tag_disciplines, -> { where("shixun = 1") }, class_name: "TagDiscipline"
has_many :subject_tag_disciplines, -> { where("subject = 1") }, class_name: "TagDiscipline"
has_many :question_tag_disciplines, -> { where("question = 1") }, class_name: "TagDiscipline"
end

@ -0,0 +1,8 @@
class TagDiscipline < ApplicationRecord
belongs_to :sub_discipline
has_many :tag_discipline_containers, dependent: :destroy
def discipline
sub_discipline&.discipline
end
end

@ -0,0 +1,5 @@
class TagDisciplineContainer < ApplicationRecord
belongs_to :tag_discipline
belongs_to :container, polymorphic: true, optional: true
end

@ -153,6 +153,10 @@ class User < ApplicationRecord
has_many :hacks, dependent: :destroy has_many :hacks, dependent: :destroy
has_many :hack_user_lastest_codes, dependent: :destroy has_many :hack_user_lastest_codes, dependent: :destroy
# 题库
has_many :item_banks, dependent: :destroy
has_many :item_baskets, -> { order("item_baskets.position ASC") }, dependent: :destroy
# Groups and active users # Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) } scope :active, lambda { where(status: STATUS_ACTIVE) }

@ -0,0 +1,32 @@
class ExaminationBankQuery < ApplicationQuery
include CustomSortable
attr_reader :params
sort_columns :updated_at, default_by: :updated_at, default_direction: :desc, default_table: 'examination_banks'
def initialize(params)
@params = params
end
def call
if params[:public].to_i == 1
exams = ExaminationBank.where(public: 1)
elsif params[:public].to_i == 0
exams = ExaminationBank.where(user_id: User.current.id)
end
if params[:tag_discipline_id].present?
exams = exams.joins(:tag_discipline_containers).where(tag_discipline_containers: {tag_discipline_id: params[:tag_discipline_id]})
elsif params[:sub_discipline_id].present?
exams = exams.where(sub_discipline_id: params[:sub_discipline_id])
elsif params[:discipline_id].present?
exams = exams.joins(:sub_discipline).where(sub_disciplines: {discipline_id: params[:discipline_id]})
end
exams = exams.where(difficulty: params[:difficulty].to_i) if params[:difficulty].present?
exams = exams.where("name like ?", "%#{params[:keyword].strip}%") if params[:keyword].present?
custom_sort(exams, params[:sort_by], params[:sort_direction])
end
end

@ -0,0 +1,33 @@
class ItemBankQuery < ApplicationQuery
include CustomSortable
attr_reader :params
sort_columns :updated_at, default_by: :updated_at, default_direction: :desc, default_table: 'item_banks'
def initialize(params)
@params = params
end
def call
if params[:public].to_i == 1
items = ItemBank.where(public: 1)
elsif params[:public].to_i == 0
items = ItemBank.where(user_id: User.current.id)
end
if params[:tag_discipline_id].present?
items = items.joins(:tag_discipline_containers).where(tag_discipline_containers: {tag_discipline_id: params[:tag_discipline_id]})
elsif params[:sub_discipline_id].present?
items = items.where(sub_discipline_id: params[:sub_discipline_id])
elsif params[:discipline_id].present?
items = items.joins(:sub_discipline).where(sub_disciplines: {discipline_id: params[:discipline_id]})
end
items = items.where(item_type: params[:item_type].to_i) if params[:item_type].present?
items = items.where(difficulty: params[:difficulty].to_i) if params[:difficulty].present?
items = items.where("name like ?", "%#{params[:keyword].strip}%") if params[:keyword].present?
custom_sort(items, params[:sort_by], params[:sort_direction])
end
end

@ -0,0 +1,52 @@
class Admins::ImportDisciplineService < ApplicationService
Error = Class.new(StandardError)
attr_reader :file, :result
def initialize(file)
@file = file
@result = { success: 0, fail: [] }
end
def call
raise Error, '文件不存在' if file.blank?
excel = Admins::ImportDisciplineExcel.new(file)
excel.read_each(&method(:save_discipline))
result
rescue ApplicationImport::Error => ex
raise Error, ex.message
end
private
def save_discipline(data)
count = 0
discipline_name = data.discipline_name.to_s.strip
sub_discipline_name = data.sub_discipline_name.to_s.strip
return unless discipline_name.present?
discipline = Discipline.find_by(name: discipline_name)
if discipline.blank?
discipline = Discipline.create!(name: discipline_name)
count += 1
end
if sub_discipline_name.present?
sub_discipline = SubDiscipline.find_by(name: discipline_name, discipline: discipline)
if sub_discipline.blank?
SubDiscipline.create!(name: sub_discipline_name, discipline: discipline)
count += 1
end
end
result[:success] += count
rescue Exception => ex
fail_data = data.as_json
fail_data[:data] = fail_data.values.join(',')
fail_data[:message] = ex.message
result[:fail] << fail_data
end
end

@ -0,0 +1,30 @@
class ExaminationBanks::SaveExaminationBankService < ApplicationService
attr_reader :exam, :params
def initialize(exam, params)
@exam = exam
@params = params
end
def call
ExaminationBanks::SaveExamForm.new(params).validate!
exam.name = params[:name].to_s.strip
exam.difficulty = params[:difficulty]
exam.duration = params[:duration]
exam.sub_discipline_id = params[:sub_discipline_id]
exam.save!
# 知识点的创建
new_tag_discipline_ids = params[:tag_discipline_id] || []
old_tag_discipline_ids = exam.tag_discipline_containers.pluck(:tag_discipline_id)
delete_tag_discipline_ids = old_tag_discipline_ids - new_tag_discipline_ids
create_tag_discipline_ids = new_tag_discipline_ids - old_tag_discipline_ids
exam.tag_discipline_containers.where(tag_discipline_id: delete_tag_discipline_ids).destroy_all
create_tag_discipline_ids.each do |tag_id|
exam.tag_discipline_containers << TagDisciplineContainer.new(tag_discipline_id: tag_id)
end
exam
end
end

@ -0,0 +1,58 @@
class ItemBanks::SaveItemService < ApplicationService
attr_reader :item, :params
def initialize(item, params)
@item = item
@params = params
end
def call
new_record = item.new_record?
raise("不能更改题型") if !new_record && item.item_type != params[:item_type]
ItemBanks::SaveItemForm.new(params).validate!
ActiveRecord::Base.transaction do
item.item_type = params[:item_type] if new_record
item.difficulty = params[:difficulty]
item.sub_discipline_id = params[:sub_discipline_id]
item.name = params[:name].to_s.strip
item.save!
analysis = item.item_analysis || ItemAnalysis.new(item_bank_id: item.id)
analysis.analysis = params[:analysis].blank? ? nil : params[:analysis].strip
analysis.save
# 知识点的创建
new_tag_discipline_ids = params[:tag_discipline_id]
old_tag_discipline_ids = item.tag_discipline_containers.pluck(:tag_discipline_id)
delete_tag_discipline_ids = old_tag_discipline_ids - new_tag_discipline_ids
create_tag_discipline_ids = new_tag_discipline_ids - old_tag_discipline_ids
item.tag_discipline_containers.where(tag_discipline_id: delete_tag_discipline_ids).destroy_all
create_tag_discipline_ids.each do |tag_id|
item.tag_discipline_containers << TagDisciplineContainer.new(tag_discipline_id: tag_id)
end
# 选项的创建
if %W(SINGLE MULTIPLE JUDGMENT).include?(item.item_type)
old_choices = item.item_choices
new_choices = params[:choices]
new_choices.each_with_index do |choice, index|
choice_item = old_choices[index] || ItemChoice.new(item_bank_id: item.id)
choice_item.choice_text = choice[:choice_text]
choice_item.is_answer = choice[:is_answer]
choice_item.save!
end
if old_choices.length > new_choices.length
old_choices[new_choices.length..(old_choices.length-1)].each do |old_choice|
old_choice.destroy
end
end
end
end
item
end
end

@ -0,0 +1,47 @@
class ItemBaskets::SaveItemBasketService < ApplicationService
attr_reader :user, :params
def initialize(user, params)
@user = user
@params = params
end
def call
raise("请选择试题") if params[:item_ids].blank?
# 只能选用公共题库或是自己的题库
items = ItemBank.where(public: 1).or(ItemBank.where(user_id: user.id))
# 已选到过试题篮的不重复选用
item_ids = params[:item_ids] - user.item_baskets.pluck(:item_bank_id)
items.where(id: item_ids).each do |item|
new_item = ItemBasket.new(user_id: user.id, item_bank_id: item.id, item_type: item.item_type)
new_item.score = item_score item.item_type
new_item.position = item_position item.item_type
new_item.save!
end
end
private
def item_score item_type
if user.item_baskets.where(item_type: item_type).last.present?
score = user.item_baskets.where(item_type: item_type).last.score
else
score =
case item_type
when 0, 1, 2
5
when 6
10
else
5
end
end
score
end
def item_position item_type
user.item_baskets.where(item_type: item_type).last&.position.to_i + 1
end
end

@ -26,7 +26,14 @@ class CreateShixunService < ApplicationService
# 创建镜像 # 创建镜像
ShixunMirrorRepository.create!(:shixun_id => shixun.id, :mirror_repository_id => main_mirror.id) ShixunMirrorRepository.create!(:shixun_id => shixun.id, :mirror_repository_id => main_mirror.id)
# 创建主服务配置 # 创建主服务配置
ShixunServiceConfig.create!(:shixun_id => shixun.id, :mirror_repository_id => main_mirror.id) config_params =
if shixun.is_jupyter?
{shixun_id: shixun.id, mirror_repository_id: main_mirror.id, cpu_limit: 2,
memory_limit: 1024, lower_cpu_limit: 0.2, request_limit: 100}
else
{shixun_id: shixun.id, mirror_repository_id: main_mirror.id}
end
ShixunServiceConfig.create!(config_params)
# 创建子镜像相关数据(实训镜像关联表,子镜像服务配置) # 创建子镜像相关数据(实训镜像关联表,子镜像服务配置)
sub_mirrors.each do |sub| sub_mirrors.each do |sub|
ShixunMirrorRepository.create!(:shixun_id => shixun.id, :mirror_repository_id => sub.id) ShixunMirrorRepository.create!(:shixun_id => shixun.id, :mirror_repository_id => sub.id)

@ -0,0 +1,2 @@
$.notify({ message: '删除成功' });
$(".discipline-item-<%= @discipline_id %>").remove();

@ -0,0 +1,2 @@
$('.admin-modal-container').html("<%= j( render partial: 'admins/disciplines/shared/edit_discipline_modal', locals: { discipline: @discipline } ) %>");
$('.modal.admin-edit-discipline-modal').modal('show');

@ -0,0 +1,17 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('课程方向', admins_disciplines_path) %>
<% end %>
<div class="box search-form-container discipline-list-form rig">
<div class="flex-1">
<%= javascript_void_link '新增', class: 'btn btn-primary', data: { toggle: 'modal', target: '.admin-create-discipline-modal' } %>
</div>
<%= javascript_void_link '导入数据', class: 'btn btn-primary', data: { toggle: 'modal', target: '.admin-import-discipline-modal'} %>
</div>
<div class="box admin-list-container discipline-list-container">
<%= render(partial: 'admins/disciplines/shared/list') %>
</div>
<%= render 'admins/disciplines/shared/create_discipline_modal' %>
<%= render partial: 'admins/disciplines/shared/import_discipline_modal' %>

@ -0,0 +1,28 @@
<div class="modal fade admin-create-discipline-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-discipline-form" data-url="<%= admins_disciplines_path %>">
<div class="form-group d-flex">
<label for="new_mirror_id" class="col-form-label">名称:</label>
<div class="w-75 d-flex flex-column">
<%= text_field_tag(:name, nil, class: 'form-control', placeholder: '请输入名称') %>
</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,23 @@
<div class="modal fade admin-edit-discipline-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">编辑课程方向</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<%= simple_form_for([:admins, discipline], html: { class: 'admin-edit-discipline-form' }, defaults: { wrapper_html: { class: 'offset-md-1 col-md-10' } }) do |f| %>
<%= f.input :name, as: :string, label: '名称' %>
<div class="error text-danger"></div>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,30 @@
<div class="modal fade admin-import-discipline-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-import-discipline-form" enctype="multipart/form-data">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">文件</span>
</div>
<div class="custom-file">
<input type="file" name="file" class="upload-file-input" id="import-discipline-input" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
<label class="custom-file-label file-names" for="import-discipline-input">选择文件</label>
</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,33 @@
<table class="table table-hover text-center discipline-list-table">
<thead class="thead-light">
<tr>
<th width="6%">序号</th>
<th width="54%" class="text-left">课程方向</th>
<th width="8%">实践课程</th>
<th width="8%">实训</th>
<th width="8%">题库</th>
<th width="16%">操作</th>
</tr>
</thead>
<tbody>
<% if @disciplines.present? %>
<% @disciplines.each_with_index do |discipline, index| %>
<tr class="discipline-item discipline-item-<%= discipline.id %>">
<td><%= index + 1 %></td>
<td class="text-left">
<span><%= link_to discipline.name, admins_sub_disciplines_path(discipline_id: discipline), :title => discipline.name %></span>
</td>
<td><%= check_box_tag :subject,!discipline.subject,discipline.subject,remote:true,data:{id:discipline.id},class:"discipline-source-form" %></td>
<td><%= check_box_tag :shixun,!discipline.shixun,discipline.shixun,remote:true,data:{id:discipline.id},class:"discipline-source-form" %></td>
<td><%= check_box_tag :question,!discipline.question,discipline.question,remote:true,data:{id:discipline.id},class:"discipline-source-form" %></td>
<td>
<%= link_to '编辑', edit_admins_discipline_path(discipline), remote: true, class: 'action' %>
<%= delete_link '删除', admins_discipline_path(discipline, element: ".discipline-item-#{discipline.id}"), class: 'delete-discipline-action' %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>

@ -0,0 +1,2 @@
$('.modal.admin-edit-discipline-modal').modal("hide");
$(".discipline-list-container").html("<%= j(render :partial => 'admins/disciplines/shared/list') %>");

@ -38,6 +38,8 @@
<% end %> <% end %>
</li> </li>
<li><%= sidebar_item(admins_disciplines_path, '课程体系', icon: 'sitemap', controller: 'admins-disciplines') %></li>
<li> <li>
<%= sidebar_item_group('#course-submenu', '课堂管理', icon: 'book') do %> <%= sidebar_item_group('#course-submenu', '课堂管理', icon: 'book') do %>
<li><%= sidebar_item(admins_course_lists_path, '课程列表', icon: 'list', controller: 'admins-course_lists') %></li> <li><%= sidebar_item(admins_course_lists_path, '课程列表', icon: 'list', controller: 'admins-course_lists') %></li>

@ -0,0 +1,2 @@
$.notify({ message: '删除成功' });
$(".sub-discipline-item-<%= @sub_discipline_id %>").remove();

@ -0,0 +1,2 @@
$('.admin-modal-container').html("<%= j( render partial: 'admins/sub_disciplines/shared/edit_sub_discipline_modal', locals: { sub_discipline: @sub_discipline } ) %>");
$('.modal.admin-edit-sub-discipline-modal').modal('show');

@ -0,0 +1,14 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('课程方向', admins_disciplines_path) %>
<% add_admin_breadcrumb(@discipline.name) %>
<% end %>
<div class="box search-form-container sub-discipline-list-form">
<%= javascript_void_link '新增', class: 'btn btn-primary', data: { toggle: 'modal', target: '.admin-create-sub-discipline-modal' } %>
</div>
<div class="box admin-list-container sub-discipline-list-container">
<%= render(partial: 'admins/sub_disciplines/shared/list') %>
</div>
<%= render 'admins/sub_disciplines/shared/create_sub_discipline_modal' %>

@ -0,0 +1,28 @@
<div class="modal fade admin-create-sub-discipline-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-sub-discipline-form" data-url="<%= admins_sub_disciplines_path(discipline_id: @discipline) %>">
<div class="form-group d-flex">
<label for="new_mirror_id" class="col-form-label">名称:</label>
<div class="w-75 d-flex flex-column">
<%= text_field_tag(:name, nil, class: 'form-control', placeholder: '请输入名称') %>
</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,23 @@
<div class="modal fade admin-edit-sub-discipline-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">编辑课程</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<%= simple_form_for([:admins, sub_discipline], html: { class: 'admin-edit-sub-discipline-form' }, defaults: { wrapper_html: { class: 'offset-md-1 col-md-10' } }) do |f| %>
<%= f.input :name, as: :string, label: '名称' %>
<div class="error text-danger"></div>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,33 @@
<table class="table table-hover text-center sub-discipline-list-table">
<thead class="thead-light">
<tr>
<th width="6%">序号</th>
<th width="54%" class="text-left">课程</th>
<th width="8%">实践课程</th>
<th width="8%">实训</th>
<th width="8%">题库</th>
<th width="16%">操作</th>
</tr>
</thead>
<tbody>
<% if @sub_disciplines.present? %>
<% @sub_disciplines.each_with_index do |sub, index| %>
<tr class="sub-discipline-item sub-discipline-item-<%= sub.id %>">
<td><%= index + 1 %></td>
<td class="text-left">
<span><%= link_to sub.name, admins_tag_disciplines_path(sub_discipline_id: sub), :title => sub.name %></span>
</td>
<td><%= check_box_tag :subject,!sub.subject,sub.subject,disabled:!sub.discipline&.subject,remote:true,data:{id:sub.id},class:"sub-discipline-source-form" %></td>
<td><%= check_box_tag :shixun,!sub.shixun,sub.shixun,disabled:!sub.discipline&.shixun,remote:true,data:{id:sub.id},class:"sub-discipline-source-form" %></td>
<td><%= check_box_tag :question,!sub.question,sub.question,disabled:!sub.discipline&.question,remote:true,data:{id:sub.id},class:"sub-discipline-source-form" %></td>
<td>
<%= link_to '编辑', edit_admins_sub_discipline_path(sub), remote: true, class: 'action' %>
<%= delete_link '删除', admins_sub_discipline_path(sub, element: ".sub-discipline-item-#{sub.id}"), class: 'delete-sub-discipline-action' %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>

@ -0,0 +1,2 @@
$('.modal.admin-edit-sub-discipline-modal').modal("hide");
$(".sub-discipline-list-container").html("<%= j(render :partial => 'admins/sub_disciplines/shared/list') %>");

@ -0,0 +1,2 @@
$.notify({ message: '删除成功' });
$(".tag-discipline-item-<%= @tag_discipline_id %>").remove();

@ -0,0 +1,2 @@
$('.admin-modal-container').html("<%= j( render partial: 'admins/tag_disciplines/shared/edit_tag_discipline_modal', locals: { tag_discipline: @tag_discipline } ) %>");
$('.modal.admin-edit-tag-discipline-modal').modal('show');

@ -0,0 +1,15 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('课程方向', admins_disciplines_path) %>
<% add_admin_breadcrumb(@sub_discipline&.discipline&.name, admins_sub_disciplines_path(discipline_id: @sub_discipline&.discipline_id)) %>
<% add_admin_breadcrumb(@sub_discipline.name) %>
<% end %>
<div class="box search-form-container tag-discipline-list-form">
<%= javascript_void_link '新增', class: 'btn btn-primary', data: { toggle: 'modal', target: '.admin-create-tag-discipline-modal' } %>
</div>
<div class="box admin-list-container tag-discipline-list-container">
<%= render(partial: 'admins/tag_disciplines/shared/list') %>
</div>
<%= render 'admins/tag_disciplines/shared/create_tag_discipline_modal' %>

@ -0,0 +1,28 @@
<div class="modal fade admin-create-tag-discipline-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-tag-discipline-form" data-url="<%= admins_tag_disciplines_path(sub_discipline_id: @sub_discipline) %>">
<div class="form-group d-flex">
<label for="new_mirror_id" class="col-form-label">名称:</label>
<div class="w-75 d-flex flex-column">
<%= text_field_tag(:name, nil, class: 'form-control', placeholder: '请输入名称') %>
</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,23 @@
<div class="modal fade admin-edit-tag-discipline-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">编辑知识点</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<%= simple_form_for([:admins, tag_discipline], html: { class: 'admin-edit-tag-discipline-form' }, defaults: { wrapper_html: { class: 'offset-md-1 col-md-10' } }) do |f| %>
<%= f.input :name, as: :string, label: '名称' %>
<div class="error text-danger"></div>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -0,0 +1,40 @@
<table class="table table-hover text-center tag-discipline-list-table">
<thead class="thead-light">
<tr>
<th width="6%">序号</th>
<th width="54%" class="text-left">知识点</th>
<th width="8%">实践课程</th>
<th width="8%">实训</th>
<th width="8%">题库</th>
<th width="16%">操作</th>
</tr>
</thead>
<tbody>
<% if @tag_disciplines.present? %>
<% @tag_disciplines.each_with_index do |tag, index| %>
<tr class="tag-discipline-item tag-discipline-item-<%= tag.id %>">
<td><%= index + 1 %></td>
<td class="text-left"><%= tag.name %></td>
<td>
<% disabled = !(tag.sub_discipline&.subject && tag.discipline&.subject) %>
<%= check_box_tag :subject,!tag.subject,tag.subject,disabled:disabled,remote:true,data:{id:tag.id},class:"tag-discipline-source-form" %>
</td>
<td>
<% disabled = !(tag.sub_discipline&.shixun && tag.discipline&.shixun) %>
<%= check_box_tag :shixun,!tag.shixun,tag.shixun,disabled:disabled,remote:true,data:{id:tag.id},class:"tag-discipline-source-form" %>
</td>
<td>
<% disabled = !(tag.sub_discipline&.question && tag.discipline&.question) %>
<%= check_box_tag :question,!tag.question,tag.question,disabled:disabled,remote:true,data:{id:tag.id},class:"tag-discipline-source-form" %>
</td>
<td>
<%= link_to '编辑', edit_admins_tag_discipline_path(tag), remote: true, class: 'action' %>
<%= delete_link '删除', admins_tag_discipline_path(tag, element: ".tag-discipline-item-#{tag.id}"), class: 'delete-tag-discipline-action' %>
</td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>

@ -0,0 +1,2 @@
$('.modal.admin-edit-tag-discipline-modal').modal("hide");
$(".tag-discipline-list-container").html("<%= j(render :partial => 'admins/tag_disciplines/shared/list') %>");

@ -0,0 +1,35 @@
case params[:source]
when 'subject'
disciplines = Discipline.where(subject: 1).includes(subject_sub_disciplines: :subject_tag_disciplines)
json.disciplines disciplines do |dis|
json.(dis, :id, :name)
json.sub_disciplines dis.subject_sub_disciplines do |sub|
json.(sub, :id, :name)
json.tag_disciplines sub.subject_tag_disciplines do |tag|
json.(tag, :id, :name)
end
end
end
when 'shixun'
disciplines = Discipline.where(shixun: 1).includes(shixun_sub_disciplines: :shixun_tag_disciplines)
json.disciplines disciplines do |dis|
json.(dis, :id, :name)
json.sub_disciplines dis.shixun_sub_disciplines do |sub|
json.(sub, :id, :name)
json.tag_disciplines sub.shixun_tag_disciplines do |tag|
json.(tag, :id, :name)
end
end
end
when 'question'
disciplines = Discipline.where(question: 1).includes(question_sub_disciplines: :question_tag_disciplines)
json.disciplines disciplines do |dis|
json.(dis, :id, :name)
json.sub_disciplines dis.question_sub_disciplines do |sub|
json.(sub, :id, :name)
json.tag_disciplines sub.question_tag_disciplines do |tag|
json.(tag, :id, :name)
end
end
end
end

@ -0,0 +1,11 @@
json.exams @exams.each do |exam|
json.(exam, :id, :name, :difficulty, :quotes)
json.question_count exam.question_count
json.total_score exam.total_score
json.update_time exam.updated_at&.strftime("%Y-%m-%d %H:%M")
json.author do
json.login exam.user&.login
json.name exam.user&.full_name
end
end
json.exam_count @exams_count

@ -0,0 +1,6 @@
json.(item, :name, :item_type, :difficulty, :public, :quotes)
json.analysis item.analysis
json.choices item.item_choices do |choice|
json.choice_text choice.choice_text
json.is_answer choice.is_answer
end

@ -0,0 +1,15 @@
json.discipline do
json.(@item.sub_discipline&.discipline, :id, :name)
end
json.sub_discipline do
json.(@item.sub_discipline, :id, :name)
end
json.tag_disciplines @item.tag_disciplines do |tag|
json.(tag, :id, :name)
end
json.(@item, :id, :name, :item_type, :difficulty)
json.analysis @item.analysis
json.choices @item.item_choices do |choice|
json.choice_text choice.choice_text
json.is_answer choice.is_answer
end

@ -0,0 +1,11 @@
json.items @items.each do |item|
json.id item.id
json.partial! "item_banks/item", locals: {item: item}
json.update_time item.updated_at&.strftime("%Y-%m-%d %H:%M")
json.choosed @item_basket_ids.include?(item.id)
json.author do
json.login item.user&.login
json.name item.user&.full_name
end
end
json.items_count @items_count

@ -0,0 +1,7 @@
json.single_questions_count @single_questions_count
json.multiple_questions_count @multiple_questions_count
json.judgement_questions_count @judgement_questions_count
json.completion_questions_count @completion_questions_count
json.subjective_questions_count @subjective_questions_count
json.practical_questions_count @practical_questions_count
json.program_questions_count @program_questions_count

@ -0,0 +1,3 @@
json.status 0
json.questions_score @questions_score
json.all_score @all_score

@ -0,0 +1,38 @@
json.single_questions do
json.questions @single_questions.each do |question|
json.(question, :id, :position, :score, :item_type)
json.partial! "item_banks/item", locals: {item: question.item_bank}
end
json.questions_score @single_questions.map(&:score).sum
json.questions_count @single_questions.size
end
json.multiple_questions do
json.questions @multiple_questions.each do |question|
json.(question, :id, :position, :score, :item_type)
json.partial! "item_banks/item", locals: {item: question.item_bank}
end
json.questions_score @multiple_questions.map(&:score).sum
json.questions_count @multiple_questions.size
end
json.judgement_questions do
json.questions @judgement_questions.each do |question|
json.(question, :id, :position, :score, :item_type)
json.partial! "item_banks/item", locals: {item: question.item_bank}
end
json.questions_score @judgement_questions.map(&:score).sum
json.questions_count @judgement_questions.size
end
json.program_questions do
json.questions @program_questions.each do |question|
json.(question, :id, :position, :score, :item_type)
json.partial! "item_banks/item", locals: {item: question.item_bank}
end
json.questions_score @program_questions.map(&:score).sum
json.questions_count @program_questions.size
end
json.all_score @item_baskets.map(&:score).sum
json.all_questions_count @item_baskets.size

@ -0,0 +1,3 @@
json.status 0
json.questions_score @questions_score
json.all_score @all_score

@ -55,10 +55,32 @@ Rails.application.routes.draw do
end end
end end
resources :disciplines, only: [:index]
resources :item_banks do resources :item_banks do
member do
post :set_public
end
end
resources :item_baskets do
collection do
get :basket_list
delete :delete_item_type
post :batch_set_score
end end
member do
post :set_score
post :adjust_position
end
end
resources :examination_banks do
member do
post :set_public
end
end
resources :hacks, path: :problems, param: :identifier do resources :hacks, path: :problems, param: :identifier do
collection do collection do
@ -1056,6 +1078,7 @@ Rails.application.routes.draw do
post :reset_login_times post :reset_login_times
end end
end end
resource :import_disciplines, only: [:create]
resource :import_users, only: [:create] resource :import_users, only: [:create]
resource :import_course_members, only: [:create] resource :import_course_members, only: [:create]
resources :user_statistics, only: [:index] do resources :user_statistics, only: [:index] do
@ -1252,6 +1275,10 @@ Rails.application.routes.draw do
resources :courses, only: [:index, :destroy, :update] resources :courses, only: [:index, :destroy, :update]
resources :projects, only: [:index, :destroy] resources :projects, only: [:index, :destroy]
resources :disciplines, only: [:index, :create, :edit, :update, :destroy]
resources :sub_disciplines, only: [:index, :create, :edit, :update, :destroy]
resources :tag_disciplines, only: [:index, :create, :edit, :update, :destroy]
end end
namespace :cooperative do namespace :cooperative do

@ -0,0 +1,7 @@
class MigrateItemBankColumn < ActiveRecord::Migration[5.2]
def change
remove_column :item_banks, :curriculum_id
remove_column :item_banks, :curriculum_direction_id
add_column :item_banks, :sub_repertoire_id, :integer, index: true
end
end

@ -0,0 +1,7 @@
class ChangeItemBankPublicDefault < ActiveRecord::Migration[5.2]
def change
change_column_default :item_banks, :public, 0
change_column_default :item_banks, :quotes, 0
change_column_default :item_banks, :difficulty, 1
end
end

@ -0,0 +1,7 @@
class AddPositionScoreToBasket < ActiveRecord::Migration[5.2]
def change
add_column :item_baskets, :position, :integer, default: 0
add_column :item_baskets, :score, :float, default: 0
add_column :item_baskets, :item_type, :integer, default: 0
end
end

@ -0,0 +1,12 @@
class CreateDisciplines < ActiveRecord::Migration[5.2]
def change
create_table :disciplines do |t|
t.string :name
t.boolean :subject
t.boolean :shixun
t.boolean :question
t.timestamps
end
end
end

@ -0,0 +1,13 @@
class CreateSubDisciplines < ActiveRecord::Migration[5.2]
def change
create_table :sub_disciplines do |t|
t.references :discipline, index: true
t.string :name
t.boolean :subject
t.boolean :shixun
t.boolean :question
t.timestamps
end
end
end

@ -0,0 +1,13 @@
class CreateTagDisciplines < ActiveRecord::Migration[5.2]
def change
create_table :tag_disciplines do |t|
t.references :sub_discipline, index: true
t.string :name
t.boolean :subject
t.boolean :shixun
t.boolean :question
t.timestamps
end
end
end

@ -0,0 +1,6 @@
class MigrateItemBankTagColumn < ActiveRecord::Migration[5.2]
def change
remove_column :item_banks, :sub_repertoire_id
add_column :item_banks, :sub_discipline_id, :integer, index: true
end
end

@ -0,0 +1,13 @@
class CreateTagDisciplineContainers < ActiveRecord::Migration[5.2]
def change
create_table :tag_discipline_containers do |t|
t.references :tag_discipline, index: true
t.integer :container_id
t.string :container_type
t.timestamps
end
add_index :tag_discipline_containers, [:container_type, :container_id], name: "index_on_container"
end
end

@ -0,0 +1,15 @@
class MigrateDisciplinesDefault < ActiveRecord::Migration[5.2]
def change
change_column_default :disciplines, :subject, from: nil, to: 1
change_column_default :disciplines, :shixun, from: nil, to: 1
change_column_default :disciplines, :question, from: nil, to: 1
change_column_default :sub_disciplines, :subject, from: nil, to: 1
change_column_default :sub_disciplines, :shixun, from: nil, to: 1
change_column_default :sub_disciplines, :question, from: nil, to: 1
change_column_default :tag_disciplines, :subject, from: nil, to: 1
change_column_default :tag_disciplines, :shixun, from: nil, to: 1
change_column_default :tag_disciplines, :question, from: nil, to: 1
end
end

@ -0,0 +1,15 @@
class CreateExaminationBanks < ActiveRecord::Migration[5.2]
def change
create_table :examination_banks do |t|
t.string :name
t.integer :difficulty, default: 1
t.references :user, index: true
t.boolean :public, default: 0
t.integer :quotes, default: 0
t.references :sub_discipline, index: true
t.integer :duration
t.timestamps
end
end
end

@ -0,0 +1,15 @@
class CreateExaminationItems < ActiveRecord::Migration[5.2]
def change
create_table :examination_items do |t|
t.references :examination_bank, index: true
t.references :item_bank, index: true
t.string :name
t.integer :item_type, default: 0
t.integer :difficulty, default: 1
t.float :score, default: 0
t.integer :position, default: 0
t.timestamps
end
end
end

@ -0,0 +1,11 @@
class CreateExaminationItemChoices < ActiveRecord::Migration[5.2]
def change
create_table :examination_item_choices do |t|
t.references :examination_item, index: true
t.text :choice_text
t.boolean :is_answer, default: 0
t.timestamps
end
end
end

@ -0,0 +1,5 @@
class AddSubDisciplineIdForHacks < ActiveRecord::Migration[5.2]
def change
add_column :hacks, :sub_discipline_id, :integer
end
end

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -30,7 +30,7 @@ const env = getClientEnvironment(publicUrl);
module.exports = { module.exports = {
// You may want 'eval' instead if you prefer to see the compiled output in DevTools. // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s
//devtool: "cheap-module-eval-source-map", devtool: "cheap-module-eval-source-map",
// 开启调试 // 开启调试
//devtool: "source-map", // 开启调试 //devtool: "source-map", // 开启调试
// These are the "entry points" to our application. // These are the "entry points" to our application.

@ -326,8 +326,8 @@ module.exports = {
comments: false comments: false
}, },
compress: { compress: {
drop_debugger: true, drop_debugger: false,
drop_console: true drop_console: false
} }
} }
}), }),

@ -301,6 +301,31 @@ const Developer = Loadable({
loader: () => import('./modules/developer'), loader: () => import('./modules/developer'),
loading: Loading loading: Loading
}) })
// 试题库
const Headplugselection = Loadable({
loader: () => import('./modules/question/Question'),
loading: Loading
})
//试题库新建 //题库编辑
const Questionitem_banks = Loadable({
loader: () => import('./modules/question/Questionitem_banks'),
loading: Loading
})
//试卷库
const Testpaperlibrary= Loadable({
loader: () => import('./modules/testpaper/Testpaperlibrary'),
loading: Loading
})
//人工组卷
const Paperreview= Loadable({
loader: () => import('./modules/question/Paperreview'),
loading: Loading
})
// 学院统计 // 学院统计
const College = Loadable({ const College = Loadable({
loader: () => import('./college/College'), loader: () => import('./college/College'),
@ -730,6 +755,19 @@ class App extends Component {
(props) => (<RecordDetail {...this.props} {...props} {...this.state} />) (props) => (<RecordDetail {...this.props} {...props} {...this.state} />)
} }
/> />
<Route path="/question/edit/:id"
render={
(props) => (<Questionitem_banks {...this.props} {...props} {...this.state} />)
} />
<Route path="/question/newitem"
render={
(props) => (<Questionitem_banks {...this.props} {...props} {...this.state} />)
} />
<Route path="/question/:type"
render={
(props) => (<Headplugselection {...this.props} {...props} {...this.state} />)
} />
<Route path="/myproblems/:id/:tab?" <Route path="/myproblems/:id/:tab?"
render={ render={
(props) => (<StudentStudy {...this.props} {...props} {...this.state} />) (props) => (<StudentStudy {...this.props} {...props} {...this.state} />)
@ -740,6 +778,18 @@ class App extends Component {
(props) => (<Developer {...this.props} {...props} {...this.state} />) (props) => (<Developer {...this.props} {...props} {...this.state} />)
}/> }/>
<Route path="/paperreview"
render={
(props) => (<Paperreview {...this.props} {...props} {...this.state} />)
}/>
<Route path="/paperlibrary"
render={
(props) => (<Testpaperlibrary {...this.props} {...props} {...this.state} />)
}/>
<Route path="/question"
render={
(props) => (<Headplugselection {...this.props} {...props} {...this.state} />)
}/>
<Route exact path="/" <Route exact path="/"
// component={ShixunsHome} // component={ShixunsHome}
render={ render={

@ -126,7 +126,6 @@ class Sendresource extends Component{
this.props.Cancel() this.props.Cancel()
} }
Saves=()=>{ Saves=()=>{
let {fileList,description,is_public,datatime,Radiovalue} =this.state; let {fileList,description,is_public,datatime,Radiovalue} =this.state;
let newfileList=[]; let newfileList=[];

@ -12,9 +12,14 @@ class Bottomsubmit extends Component {
cannelfun = () => { cannelfun = () => {
// window.location.href= // window.location.href=
if(this.props.Cohetepaperbool===true){
this.props.setCohetepaperbool(false);
}else {
this.props.history.replace(this.props.url); this.props.history.replace(this.props.url);
} }
}
render() { render() {

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

Loading…
Cancel
Save