竞赛成绩导入

dev_home
cxt 5 years ago
parent 4ff44c382e
commit ed125af92d

@ -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 = "<div>导入结果:成功" + data.success + "条,失败"+ data.fail.length + "条</div>";
if(data.fail.length > 0){
messageHtml += '<table class="table"><thead class="thead-light"><tr><th>数据</th><th>失败原因</th></tr></thead><tbody>';
data.fail.forEach(function(item){
messageHtml += '<tr><td>' + item.data + '</td><td>' + item.message + '</td></tr>';
});
messageHtml += '</tbody></table>'
}
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);
}
});
}
});
});

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

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

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

@ -30,4 +30,5 @@
</div>
<%= render 'admins/competitions/shared/create_competition_modal' %>
<%= render partial: 'admins/shared/modal/upload_file_modal', locals: { title: '上传图片' } %>
<%= render partial: 'admins/shared/modal/upload_file_modal', locals: { title: '上传图片' } %>
<%= render partial: 'admins/competitions/shared/import_competition_score_modal' %>

@ -0,0 +1,32 @@
<div class="modal fade admin-import-competition-score-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-competition-score-form" enctype="multipart/form-data">
<%= hidden_field_tag(:competition_id, nil) %>
<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-competition-score-input" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
<label class="custom-file-label file-names" for="import-user-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>

@ -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 %>
</td>

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

Loading…
Cancel
Save