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 += '' + item.data + ' | ' + item.message + ' |
';
+ });
+
+ messageHtml += '
'
+ }
+
+ 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