diff --git a/app/assets/javascripts/admins/competitions/index.js b/app/assets/javascripts/admins/competitions/index.js index c476dbf24..074327aa5 100644 --- a/app/assets/javascripts/admins/competitions/index.js +++ b/app/assets/javascripts/admins/competitions/index.js @@ -69,5 +69,80 @@ $(document).on('turbolinks:load', function() { }); } }); + + // 导入学生 + var $importScoreModal = $('.modal.admin-import-competition-score-modal'); + var $importScoreForm = $importScoreModal.find('form.admin-import-competition-score-form'); + var $competitionIdInput = $importScoreForm.find('input[name="competition_id"]'); + + $importScoreModal.on('show.bs.modal', function(event){ + resetFileInputFunc($importScoreModal.find('.upload-file-input')); + $importScoreModal.find('.file-names').html('选择文件'); + $importScoreModal.find('.upload-file-input').trigger('click'); + + var $link = $(event.relatedTarget); + var competitionId = $link.data('competition-id'); + $competitionIdInput.val(competitionId); + }); + + $importScoreModal.on('change', '.upload-file-input', function(e){ + var file = $(this)[0].files[0]; + $importScoreModal.find('.file-names').html(file ? file.name : '请选择文件'); + }); + + var importUserFormValid = function(){ + if($importScoreForm.find('input[name="file"]').val() == undefined || $importScoreForm.find('input[name="file"]').val().length == 0){ + $importScoreForm.find('.error').html('请选择文件'); + return false; + } + + return true; + }; + + var buildResultMessage = function(data){ + var messageHtml = "
导入结果:成功" + data.success + "条,失败"+ data.fail.length + "条
"; + + if(data.fail.length > 0){ + messageHtml += ''; + + data.fail.forEach(function(item){ + messageHtml += ''; + }); + + messageHtml += '
数据失败原因
' + item.data + '' + item.message + '
' + } + + return messageHtml; + }; + + $importScoreModal.on('click', '.submit-btn', function(){ + $importScoreForm.find('.error').html(''); + + if (importUserFormValid()) { + $('body').mLoading({ text: '正在导入...' }); + + $.ajax({ + method: 'POST', + dataType: 'json', + url: '/admins/import_competition_scores', + data: new FormData($importScoreForm[0]), + processData: false, + contentType: false, + success: function(data){ + $('body').mLoading('destroy'); + $importScoreModal.modal('hide'); + + showMessageModal(buildResultMessage(data), function(){ + window.location.reload(); + }); + }, + error: function(res){ + $('body').mLoading('destroy'); + var data = res.responseJSON; + $importScoreForm.find('.error').html(data.message); + } + }); + } + }); }); diff --git a/app/controllers/admins/import_competition_scores_controller.rb b/app/controllers/admins/import_competition_scores_controller.rb new file mode 100644 index 000000000..4ddbb3d02 --- /dev/null +++ b/app/controllers/admins/import_competition_scores_controller.rb @@ -0,0 +1,14 @@ +class Admins::ImportCompetitionScoresController < Admins::BaseController + def create + return render_error('请上传正确的文件') if params[:file].blank? || !params[:file].is_a?(ActionDispatch::Http::UploadedFile) + + result = Admins::ImportCompetitionScoreService.call(params[:file].to_io, current_competition) + render_ok(result) + rescue Admins::ImportCompetitionScoreService::Error => ex + render_error(ex) + end + + def current_competition + competition = Competition.find_by!(id: params[:competition_id]) + end +end \ No newline at end of file diff --git a/app/imports/admins/import_competition_score_excel.rb b/app/imports/admins/import_competition_score_excel.rb new file mode 100644 index 000000000..77a00566c --- /dev/null +++ b/app/imports/admins/import_competition_score_excel.rb @@ -0,0 +1,20 @@ +class Admins::ImportCompetitionScoreExcel < BaseImportXlsx + Data = Struct.new(:competition_team_id, :score) + + def read_each(&block) + sheet.each_row_streaming(pad_cells: true, offset: 1) do |row| + data = row.map(&method(:cell_value))[0..1] + block.call Data.new(*data) + end + end + + private + + def check_sheet_valid! + raise_import_error('请按照模板格式导入') if sheet.row(1).size != 2 + end + + def cell_value(obj) + obj&.cell_value&.to_s&.strip + end +end diff --git a/app/services/admins/import_competition_score_service.rb b/app/services/admins/import_competition_score_service.rb new file mode 100644 index 000000000..cbeea48ec --- /dev/null +++ b/app/services/admins/import_competition_score_service.rb @@ -0,0 +1,59 @@ +class Admins::ImportCompetitionScoreService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :file, :competition, :result + + def initialize(file, competition) + @file = file + @competition = competition + @result = { success: 0, fail: [] } + end + + def call + raise Error, '文件不存在' if file.blank? + + # 创建所有战队的得分记录 + create_all_records + + excel = Admins::ImportCompetitionScoreExcel.new(file) + excel.read_each(&method(:update_competition_score)) # 更新单个战队成绩 + + result + rescue ApplicationImport::Error => ex + raise Error, ex.message + end + + private + + def update_competition_score(data) + team = competition.competition_scores.find_by(competition_team_id: data.competition_team_id) + raise "id为#{data.id}的战队不存在" if team.blank? + team.update!(score: data.score) + + result[:success] += 1 + 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 + + def create_all_records + competition.competition_scores.destroy_all + + stage = competition.competition_stages.first + + attrs = %i[ + competition_id competition_stage_id score cost_time user_id competition_team_id created_at updated_at + ] + + CompetitionScore.bulk_insert(*attrs) do |worker| + base_attr = { competition_id: competition.id, competition_stage_id: stage&.id.to_i, + score: 0, cost_time: 0 } + competition.competition_teams.each do |team| + worker.add(base_attr.merge(user_id: team.user_id).merge(competition_team_id: team.id)) + end + end + end +end \ No newline at end of file diff --git a/app/views/admins/competitions/index.html.erb b/app/views/admins/competitions/index.html.erb index 41e061fc1..d2cb43f0b 100644 --- a/app/views/admins/competitions/index.html.erb +++ b/app/views/admins/competitions/index.html.erb @@ -30,4 +30,5 @@ <%= render 'admins/competitions/shared/create_competition_modal' %> -<%= render partial: 'admins/shared/modal/upload_file_modal', locals: { title: '上传图片' } %> \ No newline at end of file +<%= render partial: 'admins/shared/modal/upload_file_modal', locals: { title: '上传图片' } %> +<%= render partial: 'admins/competitions/shared/import_competition_score_modal' %> \ No newline at end of file diff --git a/app/views/admins/competitions/shared/_import_competition_score_modal.html.erb b/app/views/admins/competitions/shared/_import_competition_score_modal.html.erb new file mode 100644 index 000000000..5b89c4c10 --- /dev/null +++ b/app/views/admins/competitions/shared/_import_competition_score_modal.html.erb @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/app/views/admins/competitions/shared/_td.html.erb b/app/views/admins/competitions/shared/_td.html.erb index 394a41d79..8d5a4afb3 100644 --- a/app/views/admins/competitions/shared/_td.html.erb +++ b/app/views/admins/competitions/shared/_td.html.erb @@ -24,4 +24,8 @@ <% end %> <%= link_to competition.published? ? "下架" : "上架", online_switch_admins_competition_path(competition), class: 'action online-action', method: :post, remote: true %> + + <% if competition.mode != 1 %> + <%= javascript_void_link '导入成绩', class: 'action', data: { competition_id: competition.id, toggle: 'modal', target: '.admin-import-competition-score-modal'} %> + <% end %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index e111ed7ba..2cb6b454b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1060,6 +1060,7 @@ Rails.application.routes.draw do end end + resource :import_competition_scores, only: [:create] resources :competitions, only: [:index, :destroy, :create] do member do post :publish