diff --git a/app/assets/javascripts/admins/competitions/index.js b/app/assets/javascripts/admins/competitions/index.js new file mode 100644 index 000000000..c476dbf24 --- /dev/null +++ b/app/assets/javascripts/admins/competitions/index.js @@ -0,0 +1,73 @@ +$(document).on('turbolinks:load', function() { + if ($('body.admins-competitions-index-page').length > 0) { + $('.modal.admin-upload-file-modal').on('upload:success', function(e, data){ + var $imageElement = $('.competition-image-' + data.source_id); + $imageElement.attr('src', data.url); + $imageElement.show(); + $imageElement.next().html('重新上传'); + }); + } + + $(".admin-competition-list-form").on("change", '.competitions-hot-select', function () { + var s_value = $(this).get(0).checked ? 1 : 0; + var json = {}; + json["hot"] = s_value; + $.ajax({ + url: "/admins/competitions/hot_setting", + type: "POST", + dataType:'json', + data: json, + success: function(){ + $.notify({ message: '操作成功' }); + } + }); + }); + + // ============== 新增竞赛 =============== + var $modal = $('.modal.admin-create-competition-modal'); + var $form = $modal.find('form.admin-create-competition-form'); + var $competitionNameInput = $form.find('input[name="competition_name"]'); + + $form.validate({ + errorElement: 'span', + errorClass: 'danger text-danger', + rules: { + competition_name: { + required: true + } + } + }); + + // modal ready fire + $modal.on('show.bs.modal', function () { + $competitionNameInput.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); + } + }); + } + }); +}); + diff --git a/app/assets/javascripts/admins/enroll_lists/index.js b/app/assets/javascripts/admins/enroll_lists/index.js new file mode 100644 index 000000000..04bd95070 --- /dev/null +++ b/app/assets/javascripts/admins/enroll_lists/index.js @@ -0,0 +1,9 @@ +$(document).on('turbolinks:load', function() { + if($('body.admins-enroll-lists-index-page').length > 0){ + let search_form = $(".search-form"); + //导出 + $(".competition-enroll-list-form").on("click","#enroll-lists-export",function () { + window.location.href = "/admins/competitions/"+$(this).attr("data-competition-id")+"/enroll_lists.xls?" + search_form.serialize(); + }); + } +}); \ No newline at end of file diff --git a/app/assets/javascripts/admins/modals/admin-upload-file-modal.js b/app/assets/javascripts/admins/modals/admin-upload-file-modal.js index cf1333381..a53173959 100644 --- a/app/assets/javascripts/admins/modals/admin-upload-file-modal.js +++ b/app/assets/javascripts/admins/modals/admin-upload-file-modal.js @@ -4,14 +4,17 @@ $(document).on('turbolinks:load', function() { var $form = $modal.find('form.admin-upload-file-form') var $sourceIdInput = $modal.find('input[name="source_id"]'); var $sourceTypeInput = $modal.find('input[name="source_type"]'); + var $suffixInput = $modal.find('input[name="suffix"]'); $modal.on('show.bs.modal', function(event){ var $link = $(event.relatedTarget); var sourceId = $link.data('sourceId'); var sourceType = $link.data('sourceType'); + var suffix = $link.data('suffix'); $sourceIdInput.val(sourceId); $sourceTypeInput.val(sourceType); + if(suffix != undefined){ $suffixInput.val(suffix); } $modal.find('.upload-file-input').trigger('click'); }); @@ -48,6 +51,7 @@ $(document).on('turbolinks:load', function() { contentType: false, success: function(data){ $.notify({ message: '上传成功' }); + $modal.find('.file-names').html(''); $modal.trigger('upload:success', data); $modal.modal('hide'); }, diff --git a/app/assets/javascripts/admins/shixun_settings/index.js b/app/assets/javascripts/admins/shixun_settings/index.js index f99574673..8b3eee505 100644 --- a/app/assets/javascripts/admins/shixun_settings/index.js +++ b/app/assets/javascripts/admins/shixun_settings/index.js @@ -34,10 +34,17 @@ $(document).on('turbolinks:load', function() { }); $('.modal.admin-upload-file-modal').on('upload:success', function(e, data){ - var $imageElement = $('.shixun-image-' + data.source_id); - $imageElement.attr('src', data.url); - $imageElement.show(); - $imageElement.next().html('重新上传'); + if(data.suffix == '_weapp'){ + var $imageElement = $('.shixun-weapp-image-' + data.source_id); + $imageElement.attr('src', data.url); + $imageElement.show(); + $imageElement.next().html('重新上传'); + } else { + var $imageElement = $('.shixun-image-' + data.source_id); + $imageElement.attr('src', data.url); + $imageElement.show(); + $imageElement.next().html('重新上传'); + } }) } }); diff --git a/app/assets/javascripts/admins/user_statistics/index.js b/app/assets/javascripts/admins/user_statistics/index.js new file mode 100644 index 000000000..3ffb288ac --- /dev/null +++ b/app/assets/javascripts/admins/user_statistics/index.js @@ -0,0 +1,73 @@ +$(document).on('turbolinks:load', function() { + if ($('body.admins-user-statistics-index-page').length > 0) { + var $form = $('.user-statistic-list-form'); + + // ************** 学校选择 ************* + 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) { + $form.find('.school-select').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) { + $form.find('#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); + } + }); + + // 清空 + $form.on('click', '.clear-btn', function(){ + $form.find('select[name="date"]').val(''); + $form.find('.school-select').val('').trigger('change'); + $form.find('input[type="submit"]').trigger('click'); + }) + + + // 导出 + $('.export-action').on('click', function(){ + var form = $(".user-statistic-list-form .search-form") + var exportLink = $(this); + var date = form.find("select[name='date']").val(); + var schoolId = form.find('input[name="school_id"]').val(); + + var url = exportLink.data("url").split('?')[0] + "?date=" + date + "&school_id=" + schoolId; + window.open(url); + }); + } +}); \ No newline at end of file diff --git a/app/controllers/admins/competitions_controller.rb b/app/controllers/admins/competitions_controller.rb index 3b9b63243..2e2dbd4e2 100644 --- a/app/controllers/admins/competitions_controller.rb +++ b/app/controllers/admins/competitions_controller.rb @@ -11,18 +11,34 @@ class Admins::CompetitionsController < Admins::BaseController ids = @competitions.map(&:id) @member_count_map = TeamMember.where(competition_id: ids).group(:competition_id).count + @competition_hot = ModuleSetting.exists?(module_type: "Competition", property: "hot") respond_to do |format| format.js format.html end end + def create + name = params[:competition_name].to_s.strip + Competition.create!(name: name) + render_ok + end + + def hot_setting + if params[:hot].to_i == 1 && !ModuleSetting.exists?(module_type: "Competition", property: "hot") + ModuleSetting.create!(module_type: "Competition", property: "hot") + elsif params[:hot].to_i == 0 && ModuleSetting.exists?(module_type: "Competition", property: "hot") + ModuleSetting.where(module_type: "Competition", property: "hot").destroy_all + end + render_ok + end + def publish - @competition.update_attributes!(:published_at, Time.now) + @competition.update_attributes!(published_at: Time.now) end def unpublish - @competition.update_attributes!(:published_at, nil) + @competition.update_attributes!(published_at: nil) end def online_switch @@ -33,10 +49,6 @@ class Admins::CompetitionsController < Admins::BaseController end end - def enroll_list - - end - private def find_competition diff --git a/app/controllers/admins/enroll_lists_controller.rb b/app/controllers/admins/enroll_lists_controller.rb new file mode 100644 index 000000000..ccac6a72d --- /dev/null +++ b/app/controllers/admins/enroll_lists_controller.rb @@ -0,0 +1,25 @@ +class Admins::EnrollListsController < Admins::BaseController + + def index + @competition = current_competition + params[:sort_by] = params[:sort_by].presence || 'created_at' + params[:sort_direction] = params[:sort_direction].presence || 'desc' + enroll_lists = Admins::CompetitionEnrollListQuery.call(@competition, params) + + @params_page = params[:page] || 1 + @enroll_lists = paginate enroll_lists.preload(competition_team: [:user, :teachers], user: { user_extension: :school }) + @personal = @competition.personal? + + respond_to do |format| + format.js + format.html + format.xls + end + end + + private + def current_competition + @_current_competition ||= Competition.find(params[:competition_id]) + end + +end \ No newline at end of file diff --git a/app/controllers/admins/files_controller.rb b/app/controllers/admins/files_controller.rb index b269f8e27..cb8b66119 100644 --- a/app/controllers/admins/files_controller.rb +++ b/app/controllers/admins/files_controller.rb @@ -6,7 +6,12 @@ class Admins::FilesController < Admins::BaseController Util.write_file(@file, file_path) - render_ok(source_id: params[:source_id], source_type: params[:source_type].to_s, url: file_url + "?t=#{Random.rand}") + render_ok( + source_id: params[:source_id], + source_type: params[:source_type].to_s, + suffix: params[:suffix].presence, + url: file_url + ) rescue StandardError => ex logger_error(ex) render_error('上传失败') @@ -33,14 +38,14 @@ class Admins::FilesController < Admins::BaseController @_file_path ||= begin case params[:source_type].to_s when 'Shixun' then - Util::FileManage.disk_filename('Shixun', params[:source_id]) + Util::FileManage.disk_filename('Shixun', params[:source_id], params[:suffix].presence) else - Util::FileManage.disk_filename(params[:source_type].to_s, params[:source_id].to_s) + Util::FileManage.disk_filename(params[:source_type].to_s, params[:source_id].to_s, params[:suffix].presence) end end end def file_url - Util::FileManage.disk_file_url(params[:source_type].to_s, params[:source_id].to_s) + Util::FileManage.disk_file_url(params[:source_type].to_s, params[:source_id].to_s, params[:suffix].presence) end end \ No newline at end of file diff --git a/app/controllers/admins/user_statistics_controller.rb b/app/controllers/admins/user_statistics_controller.rb new file mode 100644 index 000000000..71dc3096a --- /dev/null +++ b/app/controllers/admins/user_statistics_controller.rb @@ -0,0 +1,19 @@ +class Admins::UserStatisticsController < Admins::BaseController + def index + default_sort('finish_shixun_count', 'desc') + + total_count, users = Admins::UserStatisticQuery.call(params) + + @users = paginate users, total_count: total_count + end + + def export + default_sort('finish_shixun_count', 'desc') + + params[:per_page] = 10000 + _count, @users = Admins::UserStatisticQuery.call(params) + + filename = ['用户实训情况', Time.zone.now.strftime('%Y%m%d%H%M%S')].join('-') << '.xlsx' + render xlsx: 'export', filename: filename + end +end \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d94325a5b..df3e9c96e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -623,4 +623,13 @@ class ApplicationController < ActionController::Base end user end + + # 记录热门搜索关键字 + def record_search_keyword + keyword = params[:keyword].to_s.strip + return if keyword.blank? || keyword.size <= 1 + return unless HotSearchKeyword.available? + + HotSearchKeyword.add(keyword) + end end diff --git a/app/controllers/competitions/competition_teams_controller.rb b/app/controllers/competitions/competition_teams_controller.rb index e03810b61..6a4dbfd34 100644 --- a/app/controllers/competitions/competition_teams_controller.rb +++ b/app/controllers/competitions/competition_teams_controller.rb @@ -1,5 +1,65 @@ class Competitions::CompetitionTeamsController < Competitions::BaseController + before_action :tech_mode, only: [:shixun_detail, :course_detail] + + def shixun_detail + start_time = current_competition.competition_mode_setting&.start_time || current_competition.start_time + end_time = current_competition.competition_mode_setting&.end_time || current_competition.end_time + + team_user_ids = @team.team_members.pluck(:user_id) + shixuns = Shixun.where(user_id: team_user_ids, status: 2) + .where('shixuns.created_at > ? && shixuns.created_at <= ?', start_time, end_time) + shixuns = shixuns.joins('left join shixuns forked_shixuns on forked_shixuns.fork_from = shixuns.id and forked_shixuns.status = 2') + shixuns = shixuns.select('shixuns.id, shixuns.identifier, shixuns.user_id, shixuns.myshixuns_count, shixuns.name, shixuns.fork_from, sum(forked_shixuns.myshixuns_count) forked_myshixun_count') + @shixuns = shixuns.group('shixuns.id').order('shixuns.myshixuns_count desc').includes(:user) + + shixun_ids = @shixuns.map(&:id) + @myshixun_count_map = get_valid_myshixun_count(shixun_ids) + @original_myshixun_count_map = @myshixun_count_map.clone + # forked shixun valid myshixun count + forked_shixun_map = Shixun.where(status: 2, fork_from: shixun_ids).select('id, fork_from') + forked_shixun_map = forked_shixun_map.each_with_object({}) { |sx, obj| obj[sx.id] = sx.fork_from } + @forked_myshixun_count_map = get_valid_myshixun_count(forked_shixun_map.keys) + @forked_myshixun_count_map.each { |k, v| @myshixun_count_map[forked_shixun_map[k]] += v } + + @course_count_map = get_valid_course_count(shixun_ids) + @forked_map = get_valid_course_count(forked_shixun_map.keys) + @forked_course_count_map = {} + @forked_map.each do |forked_id, course_count| + @forked_course_count_map[forked_shixun_map[forked_id]] ||= 0 + @forked_course_count_map[forked_shixun_map[forked_id]] += course_count + end + @forked_shixun_map = forked_shixun_map + end + + def course_detail + start_time = current_competition.competition_mode_setting&.start_time || current_competition.start_time + end_time = current_competition.competition_mode_setting&.end_time || current_competition.end_time + @team_user_ids = @team.team_members.pluck(:user_id) + + student_count_subquery = CourseMember.where('course_id = courses.id AND role = 4').select('count(*)').to_sql + subquery = StudentWork.where('homework_common_id = hcs.id') + .select('sum(compelete_status !=0 ) as finish, count(*) as total') + .having('total != 0 and finish >= (total / 2)').to_sql + course_ids = Course.where('courses.created_at > ?', start_time) + .where('courses.created_at <= ?', end_time) + .where("(#{student_count_subquery}) >= 3") + .where("exists(select 1 from homework_commons hcs where hcs.course_id = courses.id and hcs.publish_time is not null and hcs.publish_time < NOW() and hcs.homework_type = 4 and exists(#{subquery}))") + .joins('join course_members on course_members.course_id = courses.id and course_members.role in (1,2,3)') + .where(course_members: { user_id: @team_user_ids }).pluck(:id) + courses = Course.where(id: course_ids).joins(:practice_homeworks).where('homework_commons.publish_time < now()') + @courses = courses.select('courses.id, courses.name, courses.members_count, count(*) shixun_homework_count') + .group('courses.id').order('shixun_homework_count desc').having('shixun_homework_count > 0') + + course_ids = @courses.map(&:id) + @course_myshixun_map = Myshixun.joins(student_works: :homework_common) + .where(homework_commons: { course_id: course_ids }) + .where('exists(select 1 from games where games.myshixun_id = myshixuns.id and games.status = 2)') + .group('homework_commons.course_id').count + @course_shixun_count_map = get_valid_shixun_count(course_ids) + end + def index + @personal = current_competition.personal? admin_or_business? ? all_competition_teams : user_competition_teams end @@ -67,4 +127,37 @@ class Competitions::CompetitionTeamsController < Competitions::BaseController def save_params params.permit(:name, teacher_ids: [], member_ids: []) end + + def tech_mode + # render_not_found if current_competition.mode != 3 + @team = current_competition.competition_teams.find_by(id: params[:id]) + end + + def get_valid_myshixun_count(ids) + Myshixun.where(shixun_id: ids) + .where('exists(select 1 from games where games.myshixun_id = myshixuns.id and games.status = 2)') + .group('shixun_id').count + end + + def get_valid_course_count(ids) + percentage_sql = StudentWork.where('homework_common_id = homework_commons.id and homework_commons.publish_time is not null and homework_commons.publish_time < NOW()') + .select('sum(compelete_status !=0 ) as finish, count(*) as total') + .having('total != 0 and finish >= (total / 2)').to_sql + + Course.joins(practice_homeworks: :homework_commons_shixun) + .where('shixun_id in (?)', ids) + .where("exists (#{percentage_sql})") + .group('shixun_id').count + end + + def get_valid_shixun_count(ids) + percentage_sql = StudentWork.where('homework_common_id = homework_commons.id and homework_commons.publish_time is not null and homework_commons.publish_time < NOW()') + .select('sum(compelete_status !=0 ) as finish, count(*) as total') + .having('total != 0 and finish >= (total / 2)').to_sql + Shixun.joins(homework_commons_shixuns: :homework_common) + .where(homework_commons: { homework_type: 4 }) + .where('course_id in (?)', ids) + .where("exists (#{percentage_sql})") + .group('course_id').count + end end diff --git a/app/controllers/competitions/competitions_controller.rb b/app/controllers/competitions/competitions_controller.rb index 77159fdaf..a17564737 100644 --- a/app/controllers/competitions/competitions_controller.rb +++ b/app/controllers/competitions/competitions_controller.rb @@ -1,6 +1,10 @@ class Competitions::CompetitionsController < Competitions::BaseController + include CompetitionsHelper + skip_before_action :require_login before_action :allow_visit, except: [:index] + before_action :require_admin, only: [:update, :update_inform] + before_action :chart_visible, only: [:charts, :chart_rules] def index # 已上架 或者 即将上架 @@ -25,10 +29,108 @@ class Competitions::CompetitionsController < Competitions::BaseController end def show + @competition = current_competition + end + + def update + @competition.update_attributes!(introduction: params[:introduction]) + normal_status("更新成功") end def common_header + @competition = current_competition + @competition_modules = @competition.unhidden_competition_modules + @user = current_user + end + + def informs + status = params[:status] || 1 + @informs = current_competition.informs.where(status: status) + end + + def update_inform + tip_exception("标题和内容不能为空") if params[:name].blank? || params[:description].blank? + tip_exception("status参数有误") if params[:status].blank? || ![1, 2].include?(params[:status].to_i) + ActiveRecord::Base.transaction do + if params[:inform_id] + inform = current_competition.informs.find_by!(id: params[:inform_id]) + inform.update_attributes!(name: params[:name], description: params[:description]) + else + inform = current_competition.informs.create!(name: params[:name], description: params[:description], status: params[:status]) + end + Attachment.associate_container(params[:attachment_ids], inform.id, inform.class) if params[:attachment_ids] + normal_status("更新成功") + end + end + + def md_content + @md_content = CompetitionModuleMdContent.find_by!(id: params[:md_content_id]) + end + + def update_md_content + tip_exception("标题和内容不能为空") if params[:name].blank? || params[:content].blank? + ActiveRecord::Base.transaction do + if params[:md_content_id] + md_content = CompetitionModuleMdContent.find_by!(id: params[:md_content_id]) + md_content.update_attributes!(name: params[:name], content: params[:content]) + else + md_content = CompetitionModuleMdContent.create!(name: params[:name], content: params[:content]) + end + Attachment.associate_container(params[:attachment_ids], md_content.id, md_content.class) if params[:attachment_ids] + normal_status("更新成功") + end + end + + def chart_rules + @competition = current_competition + @stages = @competition.competition_stages + @rule_contents = @competition.chart_rules + end + + def update_chart_rules + tip_exception("内容不能为空") if params[:content].blank? + @competition = current_competition + @stage = @competition.competition_stages.find_by!(id: params[:stage_id]) if params[:stage_id] + chart_rule = @competition.chart_rules.where(competition_stage_id: @stage&.id).first + if chart_rule + chart_rule.update_attributes!(content: params[:content]) + else + @competition.chart_rules.create!(competition_stage_id: @stage&.id, content: params[:content]) + end + normal_status("更新成功") + end + def charts + @competition = current_competition + if params[:stage_id] + @stage = @competition.competition_stages.find_by(id: params[:stage_id]) + end + + if @competition.identifier == "gcc-annotation-2018" + @records = @competition.competition_teams.joins(:competition_scores) + .select("competition_teams.*, score, cost_time").order("score desc, cost_time desc") + else + @records = @competition.competition_teams.joins(:competition_scores).where(competition_scores: {competition_stage_id: @stage&.id.to_i}) + .select("competition_teams.*, score, cost_time").order("score desc, cost_time desc") + end + + current_team_ids = @competition.team_members.where(user_id: current_user.id).pluck(:competition_team_id).uniq + @user_ranks = @records.select{|com_team| current_team_ids.include?(com_team.id)} + @records = @records.where("score > 0") + if params[:format] == "xlsx" + @records = @records.includes(user: [user_extension: :school], team_members: :user) + respond_to do |format| + format.xlsx{ + set_export_cookies + chart_to_xlsx(@records, @competition) + exercise_export_name = "#{@competition.name}比赛成绩" + render xlsx: "#{exercise_export_name.strip}",template: "competitions/competitions/chart_list.xlsx.axlsx",locals: + {table_columns: @competition_head_cells, chart_lists: @competition_cells_column} + } + end + else + @records = @records.includes(:user, :team_members).limit(@competition.awards_count) + end end private @@ -38,9 +140,60 @@ class Competitions::CompetitionsController < Competitions::BaseController end def allow_visit - unless current_competition.published? || admin_or_business? - render_forbidden - return + render_forbidden unless current_competition.published? || admin_or_business? + end + + def chart_visible + chart_module = current_competition.competition_modules.find_by(name: "排行榜") + render_forbidden unless (chart_module.present? && !chart_module.hidden) || admin_or_business? + end + + # 竞赛成绩导出 + def chart_to_xlsx records, competition + @competition_head_cells = [] + @competition_cells_column = [] + + max_staff = competition.competition_staffs.sum(:maximum) + if max_staff < 2 # 个人赛 + @competition_head_cells = %w(序号 姓名 学校 学号) + else # 战队赛 + @competition_head_cells = %w(序号 战队名 指导老师 队员 学校) + end + + statistic_stages = competition.competition_stages.where("score_rate > 0") + statistic_stages.each do |stage| + @competition_head_cells += ["#{stage.name}得分", "#{stage.name}用时"] + end + @competition_head_cells += ["总得分", "总用时"] if statistic_stages.size > 1 + competition_scores = competition.competition_scores + + records.each_with_index do |record, index| + row_cells_column = [] + row_cells_column << index + 1 + record_user = record.user + if max_staff < 2 + row_cells_column << record_user.real_name + row_cells_column << record_user.school_name + row_cells_column << record_user.student_id + else + row_cells_column << record.name + row_cells_column << record.teachers_name + row_cells_column << record.members_name + row_cells_column << chart_school_str(record.team_members.select{|member| !member.is_teacher}.pluck(:user_id)) + end + + statistic_stages.each do |stage| + stage_score = competition_scores.select{|score| score.competition_stage_id == stage.id && score.competition_team_id == record.id}.first + row_cells_column << stage_score&.score.to_f.round(2) + row_cells_column << com_spend_time(stage_score&.cost_time.to_i) + end + + if statistic_stages.size > 1 + row_cells_column << record&.score.to_f.round(2) + row_cells_column << com_spend_time(record&.cost_time.to_i) + end + + @competition_cells_column.push(row_cells_column) end end end \ No newline at end of file diff --git a/app/controllers/concerns/base/paginate_helper.rb b/app/controllers/concerns/base/paginate_helper.rb index a10d7eb2e..7a81da2b9 100644 --- a/app/controllers/concerns/base/paginate_helper.rb +++ b/app/controllers/concerns/base/paginate_helper.rb @@ -1,6 +1,11 @@ module Base::PaginateHelper extend ActiveSupport::Concern + def default_sort(sort_by, direction) + params[:sort_by] = params[:sort_by].presence || sort_by + params[:sort_direction] = params[:sort_direction].presence || direction + end + def offset (page - 1) * per_page end diff --git a/app/controllers/exercises_controller.rb b/app/controllers/exercises_controller.rb index d71b72618..99356946f 100644 --- a/app/controllers/exercises_controller.rb +++ b/app/controllers/exercises_controller.rb @@ -1129,7 +1129,7 @@ class ExercisesController < ApplicationController :subjective_score => subjective_score, :commit_method => @answer_committed_user&.commit_method.to_i > 0 ? @answer_committed_user&.commit_method.to_i : params[:commit_method].to_i } - @answer_committed_user.update_attributes(commit_option) + @answer_committed_user.update_attributes!(commit_option) CommitExercsieNotifyJobJob.perform_later(@exercise.id, current_user.id) normal_status(0,"试卷提交成功!") else diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb index 67a2e0ee8..78f665b3d 100644 --- a/app/controllers/games_controller.rb +++ b/app/controllers/games_controller.rb @@ -11,7 +11,7 @@ class GamesController < ApplicationController include ApplicationHelper def show - uid_logger_dubug("--games show start") + uid_logger("--games show start") # 防止评测中途ajaxE被取消;3改成0是为了处理首次进入下一关的问题 update_game_parameter(@game) @@ -60,7 +60,7 @@ class GamesController < ApplicationController praise_count: praise_count, user_praise: user_praise, time_limit: time_limit, tomcat_url: edu_setting('cloud_tomcat_php'), is_teacher: is_teacher, myshixun_manager: myshixun_manager, git_url: (@shixun.vnc ? repo_url(@myshixun.repo_path) : "")} - if @shixun.vnc && @st == 0 + if @shixun.vnc get_vnc_link(@game) end @@ -439,7 +439,7 @@ class GamesController < ApplicationController path = params[:path] || path status = params[:status].to_i path = path.try(:strip) - uid_logger_dubug("--rep_content: path is #{path}") + uid_logger("--rep_content: path is #{path}") begin @content = git_fle_content(@myshixun.repo_path, path) || "" rescue Exception => e @@ -455,7 +455,7 @@ class GamesController < ApplicationController # 监测版本库HEAD是否存在,不存在则取最新的HEAD uri = "#{shixun_tomcat}/bridge/game/check" res = uri_post uri, rep_params - uid_logger_dubug("repo_content to bridge: res is #{res}") + uid_logger("repo_content to bridge: res is #{res}") # res值:0 表示正常;-1表示有错误;-2表示代码版本库没了 # if status == 0 && res @@ -507,10 +507,11 @@ class GamesController < ApplicationController else {updated_at: Time.now} end + logger.info("#############myshixuns_update: ##{myshixuns_update}") @myshixun.update_attributes!(myshixuns_update) gitUrl = repo_ip_url @myshixun.repo_path - uid_logger_dubug("#############giturl: ##{gitUrl}") + logger.info("#############giturl: ##{gitUrl}") gitUrl = Base64.urlsafe_encode64(gitUrl) shixun_tomcat = edu_setting('cloud_bridge') @@ -536,7 +537,7 @@ class GamesController < ApplicationController testSet << test_cases end - uid_logger_dubug("##############testSet: #{testSet}") + logger.info("##############testSet: #{testSet}") testCases = Base64.urlsafe_encode64(testSet.to_json) unless testSet.blank? # 评测类型: 0,1,2 用于webssh的评测, 3用于vnc @@ -559,11 +560,11 @@ class GamesController < ApplicationController # 私密仓库的设置 secret_rep = @shixun.shixun_secret_repository - uid_logger_dubug("############secret_rep: #{secret_rep}") + logger.info("############secret_rep: #{secret_rep}") if secret_rep&.repo_name secretGitUrl = repo_ip_url secret_rep.repo_path br_params.merge!({secretGitUrl: Base64.urlsafe_encode64(secretGitUrl), secretDir: secret_rep.secret_dir_path}) - uid_logger_dubug("#######br_params:#{br_params}") + logger.info("#######br_params:#{br_params}") end # 中间层交互 @@ -579,6 +580,7 @@ class GamesController < ApplicationController # 选择题评测 def choose_build + Rails.logger.error("#################{params}") # 选择题如果通关了,则不让再评测 if @game.status == 2 raise Educoder::TipException.new("您已通过该关卡") @@ -621,7 +623,7 @@ class GamesController < ApplicationController end # 批量插入评测结果 - uid_logger_dubug("#------------chooice score: #{score}") + uid_logger("#------------chooice score: #{score}") sql = "INSERT INTO outputs (game_id, test_set_position, actual_output, result, query_index, created_at, updated_at) VALUES" + str ActiveRecord::Base.connection.execute sql @@ -681,10 +683,10 @@ class GamesController < ApplicationController resubmit_identifier = @game.resubmit_identifier # 如果没有超时并且正在评测中 # 判断评测中的状态有两种:1、如果之前没有通关的,只需判断status为1即可;如果通过关,则判断game的resubmit_identifier是否更新 - uid_logger_dubug("################game_status: #{@game.status}") - uid_logger_dubug("################params[:resubmit]: #{params[:resubmit]}") - uid_logger_dubug("################resubmit_identifier: #{resubmit_identifier}") - uid_logger_dubug("################time_out: #{params[:time_out]}") + uid_logger("################game_status: #{@game.status}") + uid_logger("################params[:resubmit]: #{params[:resubmit]}") + uid_logger("################resubmit_identifier: #{resubmit_identifier}") + uid_logger("################time_out: #{params[:time_out]}") sec_key = params[:sec_key] if (params[:time_out] == "false") && ((params[:resubmit].blank? && @game.status == 1) || (params[:resubmit].present? && (params[:resubmit] != resubmit_identifier))) @@ -694,7 +696,7 @@ class GamesController < ApplicationController render :json => { running_code_status: running_code_status, running_code_message: running_code_message } end - uid_logger_dubug("##### resubmit_identifier is #{resubmit_identifier}") + uid_logger("##### resubmit_identifier is #{resubmit_identifier}") port = params[:port] score = 0 experience = 0 @@ -743,7 +745,7 @@ class GamesController < ApplicationController end end - uid_logger_dubug("game is #{@game.id}, record id is #{e_record.try(:id)}, time is**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}") + uid_logger("game is #{@game.id}, record id is #{e_record.try(:id)}, time is**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}") # 记录前端总耗时 record_consume_time = e_record.try(:pod_execute) max_mem = e_record.try(:max_mem) @@ -789,7 +791,7 @@ class GamesController < ApplicationController begin shixun_tomcat = edu_setting('cloud_bridge') uri = "#{shixun_tomcat}/bridge/webssh/delete" - uid_logger_dubug("#{current_user} => cloese_webssh digest is #{digest}") + Rails.logger.info("#{current_user} => cloese_webssh digest is #{digest}") params = {:tpiID => myshixun_id, :digestKey => digest_key, :identifier => @game.identifier} res = uri_post uri, params if res && res['code'].to_i != 0 @@ -828,7 +830,7 @@ class GamesController < ApplicationController @allowed_hidden_testset = @identity < User::EDU_GAME_MANAGER || @game.test_sets_view #解锁的用户 if max_query_index > 0 - uid_logger_dubug("max_query_index is #{max_query_index} game id is #{@game.id}, challenge_id is #{challenge.id}") + uid_logger("max_query_index is #{max_query_index} game id is #{@game.id}, challenge_id is #{challenge.id}") @qurey_test_sets = TestSet.find_by_sql("SELECT o.code, o.actual_output, o.out_put, o.result, o.test_set_position, o.ts_time, o.ts_mem, o.query_index, t.is_public, t.input, t.output, o.compile_success FROM outputs o, games g, challenges c, test_sets t where g.id=#{@game.id} and c.id=#{challenge.id} and o.query_index=#{max_query_index} @@ -879,7 +881,7 @@ class GamesController < ApplicationController end # actual_output为空表示暂时没有评测答题,不允许查看 actual_output = output.try(:actual_output).try(:strip) - has_answer << choose.answer if choose.answer.present? + #has_answer << choose.answer if choose.answer.present? # 标准答案处理,错误的不让用户查看,用-1替代 standard_answer = (actual_output.blank? || !output.try(:result)) ? -1 : choose.standard_answer result = output.try(:result) @@ -890,7 +892,7 @@ class GamesController < ApplicationController @chooses << sin_choose test_sets << sin_test_set end - @has_answer = has_answer.presence # 选择题永远都有答案 + @has_answer = true # 选择题永远都有答案 @choose_test_cases = {:had_submmit => had_submmit, :challenge_chooses_count => challenge_chooses_count, :choose_correct_num => choose_correct_num, :test_sets => test_sets} end diff --git a/app/controllers/homework_commons_controller.rb b/app/controllers/homework_commons_controller.rb index 60cf2d6c5..168d05299 100644 --- a/app/controllers/homework_commons_controller.rb +++ b/app/controllers/homework_commons_controller.rb @@ -1058,8 +1058,8 @@ class HomeworkCommonsController < ApplicationController tip_exception("截止时间不能晚于课堂结束时间(#{@course.end_date.end_of_day.strftime("%Y-%m-%d %H:%M")})") if @course.end_date.present? && params[:end_time] > strf_time(@course.end_date.end_of_day) else + tip_exception("缺少分班截止时间参数") if params[:group_end_times].blank? group_end_times = params[:group_end_times].reject(&:blank?).map{|time| time.to_time} - tip_exception("缺少截止时间参数") if group_end_times.blank? tip_exception("截止时间和分班参数的个数不一致") if group_end_times.length != group_ids.length group_end_times.each do |time| tip_exception("分班截止时间不能早于当前时间") if time <= Time.now diff --git a/app/controllers/hot_keywords_controller.rb b/app/controllers/hot_keywords_controller.rb new file mode 100644 index 000000000..e0c536e42 --- /dev/null +++ b/app/controllers/hot_keywords_controller.rb @@ -0,0 +1,7 @@ +class HotKeywordsController < ApplicationController + def index + keywords = [] + keywords = HotSearchKeyword.hot(8) if HotSearchKeyword.available? + render_ok(keywords: keywords) + end +end \ No newline at end of file diff --git a/app/controllers/myshixuns_controller.rb b/app/controllers/myshixuns_controller.rb index a9cc76296..4e587513a 100644 --- a/app/controllers/myshixuns_controller.rb +++ b/app/controllers/myshixuns_controller.rb @@ -261,7 +261,7 @@ class MyshixunsController < ApplicationController uid_logger_dubug("-- game build: file update #{@sec_key}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}") end # 隐藏代码文件 和 VNC的都不需要走版本库 - unless @hide_code || @myshixun.shixun&.vnc_evaluate + unless @hide_code || (@myshixun.shixun&.vnc_evaluate && params[:evaluate].present?) # 远程版本库文件内容 last_content = GitService.file_content(repo_path: @repo_path, path: path)["content"] content = if @myshixun.mirror_name.select {|a| a.include?("MachineLearning") || a.include?("Python")}.present? && params[:content].present? diff --git a/app/controllers/searchs_controller.rb b/app/controllers/searchs_controller.rb index 1ea1c5d05..130e8a5cd 100644 --- a/app/controllers/searchs_controller.rb +++ b/app/controllers/searchs_controller.rb @@ -1,9 +1,12 @@ class SearchsController < ApplicationController + after_action :record_search_keyword, only: [:index] + def index @results = SearchService.call(search_params) end private + def search_params params.permit(:keyword, :type, :page, :per_page) end diff --git a/app/controllers/shixuns_controller.rb b/app/controllers/shixuns_controller.rb index 98af25b7d..05c3500ee 100644 --- a/app/controllers/shixuns_controller.rb +++ b/app/controllers/shixuns_controller.rb @@ -232,10 +232,12 @@ class ShixunsController < ApplicationController # 同步私密版本库 if @shixun.shixun_secret_repository - repo_name = "#{current_user.login}/secret_#{@shixun.identifier}" + # 源仓库的的私密版本库地址 + repo_name = @shixun.shixun_secret_repository.repo_name + # 新生成的地址 fork_repository_name = "#{current_user.login}/secret_#{@new_shixun.identifier}" ShixunSecretRepository.create!(shixun_id: @new_shixun.id, - repo_name: "#{repo_name}", + repo_name: "#{fork_repository_name}", secret_dir_path: @shixun.shixun_secret_repository.secret_dir_path) GitService.fork_repository(repo_path: "#{repo_name}.git", fork_repository_path: (fork_repository_name + ".git")) end @@ -262,6 +264,11 @@ class ShixunsController < ApplicationController :mirror_repository_id => config.mirror_repository_id) end + # 同步高校限制 + @shixun.shixun_schools.each do |school| + ShixunSchool.create!(shixun_id: @new_shixun.id, school_id: school.school_id) + end + # fork版本库 logger.info("###########fork_repo_path: ######{@repo_path}") project_fork(@new_shixun, @repo_path, current_user.login) @@ -330,7 +337,10 @@ class ShixunsController < ApplicationController end rescue Exception => e uid_logger_error("copy shixun failed ##{e.message}") - g.delete_project(gshixungshixun.id) if gshixun.try(:id).present? # 异常后,如果已经创建了版本库需要删除该版本库 + # 删除版本库 + # 删除私密版本库 + GitService.delete_repository(repo_path: "#{fork_repository_name}.git") if @new_shixun.shixun_secret_repository&.repo_name + GitService.delete_repository(repo_path: @new_shixun.repo_path) if @new_shixun.repo_path tip_exception("实训Fork失败") raise ActiveRecord::Rollback end diff --git a/app/controllers/weapps/code_sessions_controller.rb b/app/controllers/weapps/code_sessions_controller.rb index b5886efd8..687605fc4 100644 --- a/app/controllers/weapps/code_sessions_controller.rb +++ b/app/controllers/weapps/code_sessions_controller.rb @@ -5,16 +5,23 @@ class Weapps::CodeSessionsController < Weapps::BaseController result = Wechat::Weapp.jscode2session(params[:code]) - # 已授权,绑定过账号 + # 能根据 code 拿到 unionid open_user = OpenUsers::Wechat.find_by(uid: result['unionid']) if open_user.present? && open_user.user successful_authentication(open_user.user) set_session_unionid(result['unionid']) logged = true else - # 新用户 + # 根据 code没拿到 unionid user_info = Wechat::Weapp.decrypt(result['session_key'], params[:encrypted_data], params[:iv]) + # 老用户,已绑定 + open_user = OpenUsers::Wechat.find_by(uid: user_info['unionId']) + if open_user.present? && open_user.user + successful_authentication(open_user.user) + logged = true + end + set_session_unionid(user_info['unionId']) end @@ -22,5 +29,7 @@ class Weapps::CodeSessionsController < Weapps::BaseController set_weapp_session_key(result['session_key']) # weapp session_key写入缓存 后续解密需要 render_ok(openid: result['openid'], logged: logged) + rescue Wechat::Error => ex + render_error(ex.message) end end \ No newline at end of file diff --git a/app/controllers/weapps/searchs_controller.rb b/app/controllers/weapps/searchs_controller.rb new file mode 100644 index 000000000..b4960e7b1 --- /dev/null +++ b/app/controllers/weapps/searchs_controller.rb @@ -0,0 +1,13 @@ +class Weapps::SearchsController < Weapps::BaseController + after_action :record_search_keyword, only: [:index] + + def index + @results = Weapps::SearchQuery.call(search_params) + end + + private + + def search_params + params.permit(:keyword, :type, :page, :per_page) + end +end \ No newline at end of file diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index dbdcaea40..1d684350c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -142,6 +142,14 @@ module ApplicationHelper end end + # 主页banner图 + def banner_img(source_type) + if File.exist?(disk_filename(source_type, "banner")) + ctime = File.ctime(disk_filename(source_type, "banner")).to_i + File.join("images/avatars", ["#{source_type}", "banner"]) + "?t=#{ctime}" + end + end + def disk_filename(source_type,source_id,image_file=nil) File.join(storage_path, "#{source_type}", "#{source_id}") end diff --git a/app/helpers/competitions_helper.rb b/app/helpers/competitions_helper.rb new file mode 100644 index 000000000..9e3e7f62a --- /dev/null +++ b/app/helpers/competitions_helper.rb @@ -0,0 +1,54 @@ +module CompetitionsHelper + def chart_school_str user_ids + chart_school_name = "" + chart_school_name = School.where(id: UserExtension.where(user_id: user_ids).pluck(:school_id).uniq).pluck(:name).join("、") + end + + # 耗时:小时、分、秒 00:00:00 + # 小于1秒钟则不显示 + def com_spend_time time + hour = time / (60*60) + min = time % (60*60) / 60 + sec = time % (60*60) % 60 + hour_str = "00" + min_str = "00" + sec_str = "00" + if hour >= 1 && hour < 10 + hour_str = "0#{hour}" + elsif hour >= 10 + hour_str = "#{hour}" + end + + if min >= 1 && min < 10 + min_str = "0#{min}" + elsif min >= 10 + min_str = "#{min}" + end + + if sec >= 1 && sec < 10 + sec_str = "0#{sec}" + elsif sec >= 10 + sec_str = "#{sec}" + end + + time = "#{hour_str} : #{min_str} : #{sec_str}" + return time + end + + def chart_stages competition + stages = [] + statistic_stages = competition.competition_stages.where("rate > 0") + if competition.end_time && competition.end_time < Time.now && statistic_stages.size > 1 + stages << {id: nil, name: "总排行榜", rate: 1.0, start_time: competition.start_time, end_time: competition.end_time} + end + + statistic_stages.each do |stage| + if stage.max_end_time && stage.max_end_time < Time.now + stages << {id: stage.id, name: "#{stage.name}排行榜", rate: stage.score_rate, start_time: stage.min_start_time, end_time: stage.max_end_time} + end + end + + stages = stages.sort { |a, b| b[:end_time] <=> a[:end_time] } if stages.size > 0 + return stages + end +end \ No newline at end of file diff --git a/app/helpers/exercises_helper.rb b/app/helpers/exercises_helper.rb index 395d67913..eadbfa05c 100644 --- a/app/helpers/exercises_helper.rb +++ b/app/helpers/exercises_helper.rb @@ -414,7 +414,7 @@ module ExercisesHelper score2 = 0.0 #填空题 score5 = 0.0 #实训题 ques_stand = [] #问题是否正确 - exercise_questions = exercise.exercise_questions.includes(:exercise_answers,:exercise_shixun_answers,:exercise_standard_answers,:exercise_shixun_challenges) + exercise_questions = exercise.exercise_questions.includes(:exercise_standard_answers,:exercise_shixun_challenges) exercise_questions&.each do |q| begin if q.question_type != 5 diff --git a/app/libs/hot_search_keyword.rb b/app/libs/hot_search_keyword.rb index f026142cb..f79d1641e 100644 --- a/app/libs/hot_search_keyword.rb +++ b/app/libs/hot_search_keyword.rb @@ -3,6 +3,7 @@ class HotSearchKeyword class << self def add(keyword) return if keyword.blank? + Rails.logger.info("[Hot Keyword] #{keyword} score increment ~") Rails.cache.data.zincrby(redis_key, 1, keyword) end diff --git a/app/libs/wechat/client.rb b/app/libs/wechat/client.rb index 5ac0a28f2..43d5b0732 100644 --- a/app/libs/wechat/client.rb +++ b/app/libs/wechat/client.rb @@ -53,13 +53,13 @@ class Wechat::Client private def request(method, url, **params) - Rails.logger.error("[wechat] request: #{method} #{url} #{params.except(:secret).inspect}") + Rails.logger.info("[wechat] request: #{method} #{url} #{params.except(:secret).inspect}") client = Faraday.new(url: BASE_SITE) response = client.public_send(method, url, params) result = JSON.parse(response.body) - Rails.logger.error("[wechat] response:#{response.status} #{result.inspect}") + Rails.logger.info("[wechat] response:#{response.status} #{result.inspect}") if response.status != 200 raise Wechat::Error.parse(result) diff --git a/app/libs/wechat/weapp.rb b/app/libs/wechat/weapp.rb index 755c9351a..9684206cd 100644 --- a/app/libs/wechat/weapp.rb +++ b/app/libs/wechat/weapp.rb @@ -34,7 +34,7 @@ class Wechat::Weapp data = cipher.update(encrypted_data) << cipher.final result = JSON.parse(data[0...-data.last.ord]) - raise Wechat::Error, '解密错误' if result.dig('watermark', 'appid') != appid + raise Wechat::Error.new(-1, '解密错误') if result.dig('watermark', 'appid') != appid result end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index a876440c3..ded4119cd 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -1,9 +1,15 @@ class ApplicationRecord < ActiveRecord::Base include NumberDisplayHelper + attr_accessor :_extra_data + self.abstract_class = true def format_time(time) time.present? ? time.strftime('%Y-%m-%d %H:%M') : '' end + + def display_extra_data(key) + _extra_data&.[](key) + end end diff --git a/app/models/chart_rule.rb b/app/models/chart_rule.rb new file mode 100644 index 000000000..de8fceaf1 --- /dev/null +++ b/app/models/chart_rule.rb @@ -0,0 +1,6 @@ +class ChartRule < ApplicationRecord + belongs_to :competition + belongs_to :competition_stage, optional: true + + validates :content, length: { maximum: 1000 } +end diff --git a/app/models/competition.rb b/app/models/competition.rb index 9055adefc..684db8fa5 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -9,12 +9,19 @@ class Competition < ApplicationRecord has_many :competition_teams, dependent: :destroy has_many :team_members, dependent: :destroy + has_many :chart_rules, dependent: :destroy + + has_many :competition_scores, dependent: :destroy has_many :competition_staffs, dependent: :destroy has_one :teacher_staff, -> { where(category: :teacher) }, class_name: 'CompetitionStaff' has_one :member_staff, -> { where.not(category: :teacher) }, class_name: 'CompetitionStaff' + has_one :competition_mode_setting, dependent: :destroy + + has_many :informs, as: :container, dependent: :destroy + has_many :attachments, as: :container, dependent: :destroy - has_many :attachments, as: :container + has_many :competition_awards, dependent: :destroy after_create :create_competition_modules @@ -48,7 +55,7 @@ class Competition < ApplicationRecord # 是否为个人赛 def personal? - competition_staffs.maximum(:maximum) == 1 + competition_staffs.maximum(:maximum) == 1 || max_num == 1 end # 报名是否结束 @@ -81,10 +88,20 @@ class Competition < ApplicationRecord member_staff.mutiple_limited? end + def max_min_stage_time + CompetitionStageSection.find_by_sql("SELECT MAX(end_time) as max_end_time, MIN(start_time) as min_start_time, + competition_stage_id FROM competition_stage_sections WHERE competition_id = #{id} + GROUP BY competition_stage_id order by competition_stage_id") + end + + def awards_count + competition_awards.pluck(:num)&.sum > 0 ? competition_awards.pluck(:num)&.sum : 20 + end + private def create_competition_modules - CompetitionModule.bulk_insert(*%i[competition_id name position]) do |worker| + CompetitionModule.bulk_insert(*%i[competition_id name position created_at updated_at]) do |worker| %w(首页 报名 通知公告 排行榜 资料下载).each_with_index do |name, index| worker.add(competition_id: id, name: name, position: index + 1) end diff --git a/app/models/competition_award.rb b/app/models/competition_award.rb new file mode 100644 index 000000000..715d82ee2 --- /dev/null +++ b/app/models/competition_award.rb @@ -0,0 +1,3 @@ +class CompetitionAward < ApplicationRecord + belongs_to :competition +end diff --git a/app/models/competition_mode_setting.rb b/app/models/competition_mode_setting.rb index b6bafa7c3..e8d6c17d7 100644 --- a/app/models/competition_mode_setting.rb +++ b/app/models/competition_mode_setting.rb @@ -1,3 +1,4 @@ class CompetitionModeSetting < ApplicationRecord - belongs_to :course + belongs_to :course, optional: true + belongs_to :competition end diff --git a/app/models/competition_module.rb b/app/models/competition_module.rb index be73bf3c1..9579533da 100644 --- a/app/models/competition_module.rb +++ b/app/models/competition_module.rb @@ -4,4 +4,21 @@ class CompetitionModule < ApplicationRecord belongs_to :competition has_one :competition_module_md_content, dependent: :destroy + + def module_url + case name + when "首页", "赛制介绍" + "/competitions/#{competition.identifier}" + when "通知公告" + "/competitions/#{competition.identifier}/informs?status=1" + when "参赛手册" + "/competitions/#{competition.identifier}/informs?status=2" + when "排行榜" + "/competitions/#{competition.identifier}/charts" + when "报名" + "/competitions/#{competition.identifier}/competition_teams" + else + url || "/competitions/#{competition.identifier}/md_content?md_content_id=#{competition_module_md_content&.id}" + end + end end diff --git a/app/models/competition_score.rb b/app/models/competition_score.rb new file mode 100644 index 000000000..ce7bad427 --- /dev/null +++ b/app/models/competition_score.rb @@ -0,0 +1,6 @@ +class CompetitionScore < ApplicationRecord + belongs_to :user + belongs_to :competition + belongs_to :competition_stage, optional: true + belongs_to :competition_team, optional: true +end diff --git a/app/models/competition_stage.rb b/app/models/competition_stage.rb index 60d4b1644..dec777b39 100644 --- a/app/models/competition_stage.rb +++ b/app/models/competition_stage.rb @@ -3,5 +3,15 @@ class CompetitionStage < ApplicationRecord has_many :competition_stage_sections, dependent: :destroy has_many :competition_entries, dependent: :destroy + has_many :competition_scores, dependent: :destroy + has_one :chart_rule, dependent: :destroy + + def min_start_time + competition_stage_sections.where("start_time is not null").pluck(:start_time).min + end + + def max_end_time + competition_stage_sections.where("end_time is not null").pluck(:end_time).max + end end \ No newline at end of file diff --git a/app/models/competition_team.rb b/app/models/competition_team.rb index 625b29421..6c2b99859 100644 --- a/app/models/competition_team.rb +++ b/app/models/competition_team.rb @@ -7,6 +7,7 @@ class CompetitionTeam < ApplicationRecord has_many :team_members, dependent: :destroy has_many :users, through: :team_members, source: :user + has_many :competition_scores, dependent: :destroy has_many :members, -> { without_teachers }, class_name: 'TeamMember' has_many :teachers, -> { only_teachers }, class_name: 'TeamMember' @@ -30,4 +31,21 @@ class CompetitionTeam < ApplicationRecord self.code = code code end + + def teachers_info + info = "" + teachers.each do |teacher| + teacher_info = "#{teacher.user.real_name}/#{teacher.user.school_name}" + info += info == "" ? teacher_info : "、#{teacher_info}" + end + info + end + + def teachers_name + teachers.map{|teacher| teacher.user.real_name}.join(",") + end + + def members_name + members.map{|member| member.user.real_name}.join(",") + end end \ No newline at end of file diff --git a/app/models/inform.rb b/app/models/inform.rb index 5caf80c5f..d486b6f11 100644 --- a/app/models/inform.rb +++ b/app/models/inform.rb @@ -3,4 +3,6 @@ class Inform < ApplicationRecord validates :name, length: { maximum: 60 } validates :description, length: { maximum: 5000 } + + has_many :attachments, as: :container, dependent: :destroy end diff --git a/app/models/laboratory_setting.rb b/app/models/laboratory_setting.rb index 32848dca2..ca62f5c91 100644 --- a/app/models/laboratory_setting.rb +++ b/app/models/laboratory_setting.rb @@ -43,7 +43,7 @@ class LaboratorySetting < ApplicationRecord navbar: [ { 'name' => '实践课程', 'link' => '/paths', 'hidden' => false }, { 'name' => '翻转课堂', 'link' => '/courses', 'hidden' => false }, - { 'name' => '实现项目', 'link' => '/shixuns', 'hidden' => false }, + { 'name' => '实训项目', 'link' => '/shixuns', 'hidden' => false }, { 'name' => '在线竞赛', 'link' => '/competitions', 'hidden' => false }, { 'name' => '教学案例', 'link' => '/moop_cases', 'hidden' => false }, { 'name' => '交流问答', 'link' => '/forums', 'hidden' => false }, diff --git a/app/models/module_setting.rb b/app/models/module_setting.rb new file mode 100644 index 000000000..36fc45e6f --- /dev/null +++ b/app/models/module_setting.rb @@ -0,0 +1,2 @@ +class ModuleSetting < ApplicationRecord +end diff --git a/app/models/shixun.rb b/app/models/shixun.rb index e0feb80ce..38df5f0d4 100644 --- a/app/models/shixun.rb +++ b/app/models/shixun.rb @@ -27,6 +27,7 @@ class Shixun < ApplicationRecord has_one :first_shixun_tag_repertoire, class_name: 'ShixunTagRepertoire' has_one :first_tag_repertoire, through: :first_shixun_tag_repertoire, source: :tag_repertoire + has_many :homework_commons_shixuns, class_name: 'HomeworkCommonsShixun' #实训的关卡 has_many :exercise_shixun_challenges, :dependent => :destroy diff --git a/app/models/user.rb b/app/models/user.rb index 103f9e0b5..135b85e25 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -237,6 +237,11 @@ class User < ApplicationRecord professional_certification end + # 学校所在的地区 + def school_province + user_extension&.school&.province || '' + end + # 用户的学校名称 def school_name user_extension&.school&.name || '' diff --git a/app/queries/admins/competition_enroll_list_query.rb b/app/queries/admins/competition_enroll_list_query.rb new file mode 100644 index 000000000..64f5dc7f8 --- /dev/null +++ b/app/queries/admins/competition_enroll_list_query.rb @@ -0,0 +1,40 @@ +class Admins::CompetitionEnrollListQuery < ApplicationQuery + include CustomSortable + + attr_reader :competition, :params + + sort_columns :created_at, :competition_team_id, default_by: :created_at, default_direction: :desc + + def initialize(competition, params) + @competition = competition + @params = params + end + + def call + members = competition.team_members + only_teacher = competition.competition_staffs.count == 1 && competition.competition_staffs.first.category == 'teacher' + members = members.where("team_members.is_teacher = 0") unless only_teacher # 只有老师报名时才显示老师,此时老师作为队员 + + + school = params[:school].to_s.strip + if school.present? + school_ids = School.where("schools.name like ?", "%#{school}%").pluck(:id) + school_ids = school_ids.size == 0 ? "(-1)" : "(" + school_ids.join(",") + ")" + members = members.joins(user: :user_extension).where("user_extensions.school_id in #{school_ids}") + end + + location = params[:location].to_s.strip + if location.present? + members = members.joins(user: { user_extension: :school }).where("schools.province like ?", "%#{location}%") + end + + # 关键字模糊查询 + keyword = params[:keyword].to_s.strip + if keyword.present? + members = members.joins(:competition_team) + .where('competition_teams.name LIKE :keyword', keyword: "%#{keyword}%") + end + + custom_sort(members, params[:sort_by], params[:sort_direction]) + end +end \ No newline at end of file diff --git a/app/queries/admins/user_statistic_query.rb b/app/queries/admins/user_statistic_query.rb new file mode 100644 index 000000000..d1811e5ab --- /dev/null +++ b/app/queries/admins/user_statistic_query.rb @@ -0,0 +1,138 @@ +class Admins::UserStatisticQuery < ApplicationQuery + include CustomSortable + + attr_reader :params + + sort_columns :study_challenge_count, :finish_challenge_count, :study_shixun_count, :finish_shixun_count, + default_by: :finish_shixun_count, default_direction: :desc + + def initialize(params) + @params = params + end + + def call + users = User.where(type: 'User').group(:id) + + users = users.joins(:user_extension).where(user_extensions: { school_id: params[:school_id] }) if params[:school_id].present? + + total = users.count.count + + # 根据排序字段进行查询 + users = query_by_sort_column(users, params[:sort_by]) + users = custom_sort(users, params[:sort_by], params[:sort_direction]) + + users = users.includes(user_extension: [:school, :department]) + users = users.limit(page_size).offset(offset).to_a + # 查询并组装其它数据 + users = package_other_data(users) + + [total, users] + end + + private + + def package_other_data(users) + ids = users.map(&:id) + + study_myshixun = Myshixun.where(user_id: ids) + finish_myshixun = Myshixun.where(user_id: ids, status: 1) + study_challenge = Game.joins(:myshixun).where(myshixuns: { user_id: ids }).where(status: [0, 1, 2]) + finish_challenge = Game.joins(:myshixun).where(myshixuns: { user_id: ids }).where(status: 2) + + if time_range.present? + study_myshixun = study_myshixun.where(updated_at: time_range) + finish_myshixun = finish_myshixun.where(updated_at: time_range) + study_challenge = study_challenge.where(updated_at: time_range) + finish_challenge = finish_challenge.where(updated_at: time_range) + end + + study_myshixun_map = study_myshixun.group(:user_id).count + finish_myshixun_map = finish_myshixun.group(:user_id).count + study_challenge_map = study_challenge.group(:user_id).count + finish_challenge_map = finish_challenge.group(:user_id).count + + users.each do |user| + user._extra_data = { + study_shixun_count: study_myshixun_map.fetch(user.id, 0), + finish_shixun_count: finish_myshixun_map.fetch(user.id, 0), + study_challenge_count: study_challenge_map.fetch(user.id, 0), + finish_challenge_count: finish_challenge_map.fetch(user.id, 0), + } + end + + users + end + + def query_by_sort_column(users, sort_by_column) + base_query_column = 'users.*' + + case sort_by_column.to_s + when 'study_shixun_count' then + users = + if time_range.present? + users.joins("LEFT JOIN myshixuns ON myshixuns.user_id = users.id "\ + "AND myshixuns.updated_at BETWEEN '#{time_range.min}' AND '#{time_range.max}'") + else + users.left_joins(:myshixuns) + end + + users.select("#{base_query_column}, COUNT(*) study_shixun_count") + when 'finish_shixun_count' then + users = + if time_range.present? + users.joins("LEFT JOIN myshixuns ON myshixuns.user_id = users.id AND myshixuns.status = 1 AND "\ + "myshixuns.updated_at BETWEEN '#{time_range.min}' AND '#{time_range.max}'") + else + users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id AND myshixuns.status = 1') + end + + users.select("#{base_query_column}, COUNT(*) finish_shixun_count") + when 'study_challenge_count' then + users = + if time_range.present? + users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id') + .joins("LEFT JOIN games ON games.myshixun_id = myshixuns.id "\ + "AND games.status IN (0,1,2) AND games.updated_at BETWEEN '#{time_range.min}' AND '#{time_range.max}'") + else + users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id') + .joins("LEFT JOIN games ON games.myshixun_id = myshixuns.id AND games.status IN (0,1,2)") + end + + users.select("#{base_query_column}, COUNT(*) study_challenge_count") + when 'finish_challenge_count' then + users = + if time_range.present? + users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id') + .joins("LEFT JOIN games ON games.myshixun_id = myshixuns.id "\ + "AND games.status = 2 AND games.updated_at BETWEEN '#{time_range.min}' AND '#{time_range.max}'") + else + users.joins('LEFT JOIN myshixuns ON myshixuns.user_id = users.id') + .joins("LEFT JOIN games ON games.myshixun_id = myshixuns.id AND games.status = 2") + end + + users.select("#{base_query_column}, COUNT(*) finish_challenge_count") + else + users + end + end + + def time_range + @_time_range ||= begin + case params[:date] + when 'weekly' then 1.weeks.ago..Time.now + when 'monthly' then 1.months.ago..Time.now + when 'quarterly' then 3.months.ago..Time.now + when 'yearly' then 1.years.ago..Time.now + else '' + end + end + end + + def page_size + params[:per_page].to_i.zero? ? 20 : params[:per_page].to_i + end + + def offset + (params[:page].to_i.zero? ? 0 : params[:page].to_i - 1) * page_size + end +end diff --git a/app/queries/weapps/search_query.rb b/app/queries/weapps/search_query.rb new file mode 100644 index 000000000..665480073 --- /dev/null +++ b/app/queries/weapps/search_query.rb @@ -0,0 +1,37 @@ +class Weapps::SearchQuery < ApplicationQuery + include ElasticsearchAble + + attr_reader :params + + def initialize(params) + @params = params + end + + def call + modal_name.search(keyword, search_options) + end + + private + + def search_options + hash = { + fields: [:name], + page: page, + per_page: per_page + } + hash.merge(where: { status: 2 }) if modal_name == Shixun + + hash + end + + def modal_name + @_modal_name ||= begin + case params[:type].to_s + when 'subject' then Subject + when 'shixun' then Shixun + when 'course' then Course + else Subject + end + end + end +end \ No newline at end of file diff --git a/app/tasks/exercise_publish_task.rb b/app/tasks/exercise_publish_task.rb index a7b533d05..4e34320ea 100644 --- a/app/tasks/exercise_publish_task.rb +++ b/app/tasks/exercise_publish_task.rb @@ -69,7 +69,7 @@ class ExercisePublishTask :subjective_score => subjective_score, :commit_method => exercise_user&.commit_method.to_i > 0 ? exercise_user&.commit_method.to_i : 3 } - exercise_user.update_attributes(commit_option) + exercise_user.update_attributes!(commit_option) end rescue Exception => e Rails.logger.info("rescue errors ___________________________#{e}") diff --git a/app/views/admins/competition_settings/show.html.erb b/app/views/admins/competition_settings/show.html.erb index e69de29bb..6272687e4 100644 --- a/app/views/admins/competition_settings/show.html.erb +++ b/app/views/admins/competition_settings/show.html.erb @@ -0,0 +1,16 @@ +<% + define_admin_breadcrumbs do + add_admin_breadcrumb('竞赛列表', admins_competitions_path) + add_admin_breadcrumb(@competition.name) + end +%> + +
序号 | +<%= sort_tag('战队ID', name: 'competition_team_id', path: admins_competition_enroll_lists_path(@competition)) %> | +战队名称 | +创建者 | +队员姓名 | +职业 | +学号 | +队员学校 | +地区 | +指导老师 | +<%= sort_tag('报名时间', name: 'created_at', path: admins_competition_enroll_lists_path(@competition)) %> | +
---|---|---|---|---|---|---|---|---|---|---|
<%= page_no %> | +<%= member.competition_team_id %> | +<%= @personal ? "--" : team.name %> | +<%= team.user.real_name %> | +<%= member.user.real_name %> | +<%= member.user.identity %> | +<%= member.user.student_id %> | +<%= member.user.school_name %> | +<%= member.user.school_province %> | +<%= @personal ? "--" : team.teachers_info %> | +<%= member.created_at.strftime('%Y-%m-%d %H:%M') %> | +