Merge branches 'competitions' and 'develop' of https://bdgit.educoder.net/Hjqreturn/educoder into ysmCompetitions

competitions
杨树明 5 years ago
commit 847d2998e0

@ -5,7 +5,69 @@ $(document).on('turbolinks:load', function() {
$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);
}
});
}
});
});

@ -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();
});
}
});

@ -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');
},

@ -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('重新上传');
}
})
}
});

@ -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);
});
}
});

@ -18,12 +18,27 @@ class Admins::CompetitionsController < Admins::BaseController
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
@ -34,10 +49,6 @@ class Admins::CompetitionsController < Admins::BaseController
end
end
def enroll_list
end
private
def find_competition

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

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

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

@ -353,10 +353,10 @@ class ApplicationController < ActionController::Base
# Post请求
def uri_post(uri, params)
begin
uid_logger("--uri_exec: params is #{params}, url is #{uri}")
uid_logger_dubug("--uri_exec: params is #{params}, url is #{uri}")
uri = URI.parse(URI.encode(uri.strip))
res = Net::HTTP.post_form(uri, params).body
logger.info("--uri_exec: .....res is #{res}")
uid_logger_dubug("--uri_exec: .....res is #{res}")
JSON.parse(res)
rescue Exception => e
uid_logger_error("--uri_exec: exception #{e.message}")
@ -367,10 +367,10 @@ class ApplicationController < ActionController::Base
# 处理返回非0就报错的请求
def interface_post(uri, params, status, message)
begin
uid_logger("--uri_exec: params is #{params}, url is #{uri}")
uid_logger_dubug("--uri_exec: params is #{params}, url is #{uri}")
uri = URI.parse(URI.encode(uri.strip))
res = Net::HTTP.post_form(uri, params).body
logger.info("--uri_exec: .....res is #{res}")
uid_logger_dubug("--uri_exec: .....res is #{res}")
res = JSON.parse(res)
if (res && res['code'] != 0)
tip_exception(status, message)
@ -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

@ -59,6 +59,7 @@ class Competitions::CompetitionTeamsController < Competitions::BaseController
end
def index
@personal = current_competition.personal?
admin_or_business? ? all_competition_teams : user_competition_teams
end

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

@ -9,6 +9,12 @@ module LoggerHelper
Rails.logger.info("##:#{current_user.try(:id)} --#{message}")
end
# debug日志
def uid_logger_dubug(message)
Rails.logger.info("##dubug-#{current_user.try(:id)} --#{message}")
end
# 以用户id开始的日志定义
def uid_logger_error(message)
Rails.logger.error("##:#{current_user.try(:id)} --#{message}")

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

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

@ -0,0 +1,7 @@
class HotKeywordsController < ApplicationController
def index
keywords = []
keywords = HotSearchKeyword.hot(8) if HotSearchKeyword.available?
render_ok(keywords: keywords)
end
end

@ -1,5 +1,5 @@
class MainController < ApplicationController
def index
render file: 'public/react/build/index', formats: [:html]
render file: 'public/react/build/index.html', :layout => false
end
end

@ -90,7 +90,7 @@ class MyshixunsController < ApplicationController
ActiveRecord::Base.transaction do
begin
t1 = Time.now
Rails.logger.info("@@@222222#{params[:jsonTestDetails]}")
uid_logger_dubug("@@@222222#{params[:jsonTestDetails]}")
jsonTestDetails = JSON.parse(params[:jsonTestDetails])
timeCost = JSON.parse(params[:timeCost])
brige_end_time = Time.parse(timeCost['evaluateEnd']) if timeCost['evaluateEnd'].present?
@ -99,7 +99,7 @@ class MyshixunsController < ApplicationController
game_id = jsonTestDetails['buildID']
sec_key = jsonTestDetails['sec_key']
logger.info("training_task_status start#1**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
uid_logger_dubug("training_task_status start-#{game_id}-1#{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
resubmit = jsonTestDetails['resubmit']
outPut = tran_base64_decode64(jsonTestDetails['outPut'])
@ -116,17 +116,14 @@ class MyshixunsController < ApplicationController
pics = params[:tpiRepoPath]
game.update_column(:picture_path, pics)
end
logger.info("training_task_status start#2**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
max_query_index = game.outputs ? (game.outputs.first.try(:query_index).to_i + 1) : 1
test_set_score = 0
unless jenkins_testsets.blank?
jenkins_testsets.each_with_index do |j_test_set, i|
logger.info("j_test_set: ############## #{j_test_set}")
actual_output = tran_base64_decode64(j_test_set['output'])
#ts_time += j_test_set['testSetTime'].to_i
# is_public = test_sets.where(:position => j_test_set['caseId']).first.try(:is_public)
logger.info "actual_output:################################################# #{actual_output}"
ts_time = format("%.2f", j_test_set['testSetTime'].to_f/1000000000).to_f if j_test_set['testSetTime']
ts_mem = format("%.2f", j_test_set['testSetMem'].to_f/1024/1024).to_f if j_test_set['testSetMem']
@ -139,15 +136,12 @@ class MyshixunsController < ApplicationController
end
end
end
uid_logger("#############status: #{status}")
record = EvaluateRecord.where(:identifier => sec_key).first
logger.info("training_task_status start#3**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
answer_deduction_percentage = (100 - game.answer_deduction) / 100.to_f # 查看答案后剩余分数的百分比.
# answer_deduction是查看答案的扣分比例
# status0表示评测成功
if status == "0"
if resubmit.present?
uid_logger("#############resubmitdaiao: #{resubmit}")
game.update_attributes!(:retry_status => 2, :resubmit_identifier => resubmit)
challenge.path.split("").each do |path|
game_passed_code(path.try(:strip), myshixun, game_id)
@ -205,7 +199,6 @@ class MyshixunsController < ApplicationController
:pod_execute => timeCost['execute'], :test_cases => test_cases_time,
:brige => timeCost['evaluateAllTime'], :return_back => return_back_time)
end
uid_logger("training_task_status start#4**#{game_id}**** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
sucess_status
# rescue Exception => e
# tip_exception(e.message)
@ -265,10 +258,10 @@ class MyshixunsController < ApplicationController
@sec_key = generate_identifier(EvaluateRecord, 12)
record = EvaluateRecord.create!(:user_id => current_user.id, :shixun_id => @myshixun.shixun_id, :game_id => game_id,
:identifier => @sec_key, :exec_time => exec_time)
uid_logger("-- game build: file update #{@sec_key}, record id is #{record.id}, time is **** #{Time.now.strftime("%Y-%m-%d %H:%M:%S.%L")}")
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?
@ -276,8 +269,8 @@ class MyshixunsController < ApplicationController
else
params[:content]
end
Rails.logger.info("###11222333####{content}")
Rails.logger.info("###222333####{last_content}")
uid_logger_dubug("###11222333####{content}")
uid_logger_dubug("###222333####{last_content}")
if content != last_content
@content_modified = 1
@ -285,8 +278,8 @@ class MyshixunsController < ApplicationController
author_name = current_user.real_name
author_email = current_user.git_mail
message = params[:evaluate] == 0 ? "System automatically submitted" : "User submitted"
uid_logger("112233#{author_name}")
uid_logger("112233#{author_email}")
uid_logger_dubug("112233#{author_name}")
uid_logger_dubug("112233#{author_email}")
@content = GitService.update_file(repo_path: @repo_path,
file_path: path,
message: message,

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

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

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

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

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

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

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

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

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

@ -55,7 +55,7 @@ class Competition < ApplicationRecord
# 是否为个人赛
def personal?
competition_staffs.maximum(:maximum) == 1
competition_staffs.maximum(:maximum) == 1 || max_num == 1
end
# 报名是否结束
@ -101,7 +101,7 @@ class Competition < ApplicationRecord
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

@ -32,6 +32,15 @@ class CompetitionTeam < ApplicationRecord
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

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

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

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

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

@ -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}")

@ -2,22 +2,26 @@
<% add_admin_breadcrumb('竞赛列表', admins_competitions_path) %>
<% end %>
<div class="box mb-5">
<div class="d-flex flex-column w-100">
<div class="d-flex">
<% imageExists = File.exist?(disk_filename("Competition", "banner")) %>
<% imageUrl = imageExists ? '/' + banner_img("Competition") + "?#{Time.now.to_i}" : '' %>
<span class="mr-3">竞赛主页banner</span>
<%= image_tag(imageUrl, width: 150, height: 50, class: "preview-image competition-image-banner mr-1", data: { toggle: 'tooltip', title: '点击预览' }) %>
<%= javascript_void_link imageExists ? '重新上传' : '上传图片', class: 'action upload-competition-image-action', data: { source_id: "banner", source_type: 'Competition', toggle: 'modal', target: '.admin-upload-file-modal' } %>
</div>
<div class="box mb-5 admin-competition-list-form">
<div class="d-flex align-items-center w-100">
<div class="flex-1 d-flex align-items-center">
<div class="mr-5">
<% imageExists = File.exist?(disk_filename("Competition", "banner")) %>
<% imageUrl = imageExists ? '/' + banner_img("Competition") + "?#{Time.now.to_i}" : '' %>
<span class="mr-3">竞赛主页banner</span>
<%= image_tag(imageUrl, width: 150, height: 50, class: "preview-image competition-image-banner mr-1", data: { toggle: 'tooltip', title: '点击预览' }) %>
<%= javascript_void_link imageExists ? '重新上传' : '上传图片', class: 'action upload-competition-image-action', data: { source_id: "banner", source_type: 'Competition', toggle: 'modal', target: '.admin-upload-file-modal' } %>
</div>
<div class="d-flex">
<label for="hot">
<%= check_box_tag :hot, (@competition_hot ? "1" : "0"), @competition_hot, class:"shixun-settings-select" %>
<span class="only_view">只看可ssh</span>
</label>
<div class="mr-5">
<label for="hot">
<%= check_box_tag :hot,(@competition_hot ? 1 : 0),@competition_hot,remote:true,data:{toggle:"tooltip",placement:"top"},class:"competitions-hot-select"%>
<span class="only_view">hot标识勾选则"在线竞赛"显示hot标识</span>
</label>
</div>
</div>
<%= javascript_void_link '新增', class: 'btn btn-primary', data: { toggle: 'modal', target: '.admin-create-competition-modal' } %>
</div>
</div>
@ -25,4 +29,5 @@
<%= render partial: 'admins/competitions/shared/list', locals: { competitions: @competitions } %>
</div>
<%= render 'admins/competitions/shared/create_competition_modal' %>
<%= render partial: 'admins/shared/modal/upload_file_modal', locals: { title: '上传图片' } %>

@ -0,0 +1 @@
$('.competitions-list-container').html("<%= j( render partial: 'admins/competitions/shared/list', locals: { competitions: @competitions } ) %>");

@ -0,0 +1,4 @@
var page_no = $("#competition-item-<%= @competition.id %>").children(":first").html();
$("#competition-item-<%= @competition.id %>").html("<%= j render partial: "admins/competitions/shared/td", locals: {competition: @competition, page_no: 1} %>");
$("#competition-item-<%= @competition.id %>").children(":first").html(page_no);
show_success_flash();

@ -0,0 +1,4 @@
var page_no = $("#competition-item-<%= @competition.id %>").children(":first").html();
$("#competition-item-<%= @competition.id %>").html("<%= j render partial: "admins/competitions/shared/td", locals: {competition: @competition, page_no: 1} %>");
$("#competition-item-<%= @competition.id %>").children(":first").html(page_no);
show_success_flash();

@ -0,0 +1,28 @@
<div class="modal fade admin-create-competition-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-competition-form" data-url="<%= admins_competitions_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(:competition_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>

@ -18,7 +18,7 @@
<% competitions.each_with_index do |competition, index| %>
<tr id="competition-item-<%= competition.id %>">
<% page_no = list_index_no(@params_page.to_i, index) %>
<%= render partial: "admins/competitions/shared/td",locals: {competition: competition, page_no: page_no} %>
<%= render partial: "admins/competitions/shared/td", locals: {competition: competition, page_no: page_no} %>
</tr>
<% end %>
<% else %>

@ -1,6 +1,6 @@
<td><%= page_no %></td>
<td class="text-left">
<span><%= link_to competition.name, enroll_list_admins_competition_path(competition), :target => "_blank", :title => competition.name %></span>
<span><%= link_to competition.name, admins_competition_enroll_lists_path(competition), :title => competition.name %></span>
</td>
<td><%= competition.sub_title %></td>
<td><%= competition.mode_type %></td>
@ -15,7 +15,7 @@
</td>
<td><%= competition.created_at.strftime('%Y-%m-%d %H:%M') %></td>
<td class="action-container">
<%= link_to '配置', admins_competition_competition_setting_path(competition), class: 'action edit-action' %>
<%= link_to '配置', admins_competition_competition_settings_path(competition), class: 'action edit-action' %>
<% if !competition.status? && competition.published_at.blank? %>
<%= link_to '发布', publish_admins_competition_path(competition), class: 'action publish-action', method: :post, remote: true %>

@ -0,0 +1,4 @@
var page_no = $("#competition-item-<%= @competition.id %>").children(":first").html();
$("#competition-item-<%= @competition.id %>").html("<%= j render partial: "admins/competitions/shared/td", locals: {competition: @competition, page_no: 1} %>");
$("#competition-item-<%= @competition.id %>").children(":first").html(page_no);
show_success_flash();

@ -0,0 +1,42 @@
<table class="table text-center shixun-settings-list-table">
<thead class="thead-light">
<tr>
<th width="4%" class="text-left">序号</th>
<th width="6%"><%= sort_tag('战队ID', name: 'competition_team_id', path: admins_competition_enroll_lists_path(@competition)) %></th>
<th width="12%">战队名称</th>
<th width="10%">创建者</th>
<th width="10%">队员姓名</th>
<th width="6%">职业</th>
<th width="12%">学号</th>
<th width="10%">队员学校</th>
<th width="6%">地区</th>
<th width="16%">指导老师</th>
<th width="8%"><%= sort_tag('报名时间', name: 'created_at', path: admins_competition_enroll_lists_path(@competition)) %></th>
</tr>
</thead>
<tbody>
<% if enroll_lists.present? %>
<% enroll_lists.each_with_index do |member, index| %>
<tr id="competition-team-member-<%= member.id %>">
<% team = member.competition_team %>
<% page_no = list_index_no(@params_page.to_i, index) %>
<td><%= page_no %></td>
<td><%= member.competition_team_id %></td>
<td><%= @personal ? "--" : team.name %></td>
<td><%= team.user.real_name %></td>
<td><%= member.user.real_name %></td>
<td><%= member.user.identity %></td>
<td><%= member.user.student_id %></td>
<td><%= member.user.school_name %></td>
<td><%= member.user.school_province %></td>
<td><%= @personal ? "--" : team.teachers_info %></td>
<td><%= member.created_at.strftime('%Y-%m-%d %H:%M') %></td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'admins/shared/paginate', locals: { objects: enroll_lists } %>

@ -0,0 +1,33 @@
<%
define_admin_breadcrumbs do
add_admin_breadcrumb('竞赛列表', admins_competitions_path)
add_admin_breadcrumb(@competition.name)
end
%>
<div class="box search-form-container flex-column mb-0 pb-0 competition-enroll-list-form">
<ul class="nav nav-tabs w-100 search-form-tabs">
<li class="nav-item">
<%= link_to '报名列表', admins_competition_enroll_lists_path(@competition), class: "nav-link search-form-tab active" %>
</li>
<li class="nav-item">
<%= link_to '获奖证书审批', admins_competition_enroll_lists_path(@competition), class: "nav-link search-form-tab" %>
</li>
</ul>
<div class="d-flex">
<%= form_tag(admins_competition_enroll_lists_path(unsafe_params), method: :get, class: 'form-inline search-form mt-3 flex-1 d-flex', remote: true) do %>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: '战队名称检索') %>
<%= text_field_tag(:school, params[:school], class: 'form-control col-sm-2 ml-3', placeholder: '队员学校名称检索') %>
<%= text_field_tag(:location, params[:location], class: 'form-control col-sm-2 ml-3', placeholder: '队员地区检索') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<%= link_to "清除", admins_competition_enroll_lists_path(@competition), class: "btn btn-default",'data-disable-with': '清除中...' %>
<% end %>
<a href="javascript:void(0)" class="btn btn-primary mt-3" id="enroll-lists-export" data-competition-id="<%= @competition.id %>" data-disable-with = '导出中...'>导出</a>
</div>
</div>
<div class="box competition-enroll-list-container">
<%= render(partial: 'admins/enroll_lists/list', locals: { enroll_lists: @enroll_lists }) %>
</div>

@ -0,0 +1 @@
$('.competition-enroll-list-container').html("<%= j( render(partial: 'admins/enroll_lists/list', locals: { enroll_lists: @enroll_lists }) ) %>");

@ -39,6 +39,7 @@
<li>
<%= sidebar_item_group('#user-submenu', '用户', icon: 'user') do %>
<li><%= sidebar_item(admins_users_path, '用户列表', icon: 'user', controller: 'admins-users') %></li>
<li><%= sidebar_item(admins_user_statistics_path, '用户实训情况', icon: 'area-chart', controller: 'admins-user_statistics') %></li>
<% end %>
</li>

@ -11,6 +11,7 @@
<form class="admin-upload-file-form" enctype="multipart/form-data">
<%= hidden_field_tag(:source_type, nil) %>
<%= hidden_field_tag(:source_id, nil) %>
<%= hidden_field_tag(:suffix, nil) %>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">文件</span>

@ -1,12 +1,12 @@
<table class="table text-center shixun-settings-list-table">
<thead class="thead-light">
<th width="4%">序号</th>
<th width="8%">ID</th>
<th width="12%" class="text-left">实训名称</th>
<th width="8%">技术平台</th>
<th width="8%">权限</th>
<th width="15%">技术体系</th>
<th width="12%">上传图片</th>
<th width="8%">上传图片</th>
<th width="8%">小程序封面</th>
<th width="5%">创建者</th>
<th width="5%">关闭</th>
<th width="4%">复制</th>

@ -1,4 +1,3 @@
<td class="shixun-line-no"><%= page_no %></td>
<td><%= shixun.identifier %></td>
<td class="text-left">
<span>
@ -21,6 +20,13 @@
<%= image_tag(imageUrl, width: 60, height: 40, class: "preview-image shixun-image-#{shixun.id}", data: { toggle: 'tooltip', title: '点击预览' }, style: imageExists ? '' : 'display:none') %>
<%= javascript_void_link imageExists ? '重新上传' : '上传图片', class: 'action upload-shixun-image-action', data: { source_id: shixun.id, source_type: 'Shixun', toggle: 'modal', target: '.admin-upload-file-modal' } %>
</td>
<td class="shixun-setting-weapp-image">
<% weappImageExists = Util::FileManage.exists?(shixun, '_weapp') %>
<% imageUrl = weappImageExists ? Util::FileManage.source_disk_file_url(shixun, '_weapp') : '' %>
<%= image_tag(imageUrl, width: 60, height: 40, class: "preview-image shixun-weapp-image-#{shixun.id}", data: { toggle: 'tooltip', title: '点击预览' }, style: weappImageExists ? '' : 'display:none') %>
<%= raw '<br/>' if weappImageExists %>
<%= javascript_void_link weappImageExists ? '重新上传' : '上传图片', class: 'action upload-shixun-weapp-image-action', data: { source_id: shixun.id, source_type: 'Shixun', suffix: '_weapp', toggle: 'modal', target: '.admin-upload-file-modal' } %>
</td>
<td><%= link_to shixun.owner.try(:real_name),"/users/#{shixun.owner.login}",target:'_blank' %></td>
<td>
<% if shixun.status.to_i < 3 %>

@ -0,0 +1,16 @@
wb = xlsx_package.workbook
wb.add_worksheet(name: '用户实训情况') do |sheet|
sheet.add_row %w(姓名 单位部门 学习关卡数 完成关卡数 学习实训数 完成实训数)
@users.each do |user|
data = [
user.real_name,
[user.school_name.presence, user.department_name.presence].compact.join(' - '),
user.display_extra_data(:study_challenge_count),
user.display_extra_data(:finish_challenge_count),
user.display_extra_data(:study_shixun_count),
user.display_extra_data(:finish_shixun_count)
]
sheet.add_row(data)
end
end

@ -0,0 +1,27 @@
<% define_admin_breadcrumbs do %>
<% add_admin_breadcrumb('用户实训情况') %>
<% end %>
<div class="box search-form-container user-statistic-list-form">
<%= form_tag(admins_user_statistics_path, method: :get, class: 'form-inline search-form flex-1', remote: true) do %>
<div class="form-group col-12 col-md-auto">
<label for="status">时间范围:</label>
<% data_arrs = [['不限', ''], ['最近一周', 'weekly'], ['最近一个月', 'monthly'], ['最近三个月', 'quarterly'], ['最近一年', 'yearly']] %>
<%= select_tag(:date, options_for_select(data_arrs, params[:date]), class: 'form-control') %>
</div>
<div class="form-group col-12 col-md-3">
<label for="school_name">所属单位:</label>
<%= hidden_field_tag(:school_id, params[:school_id]) %>
<%= select_tag :school_name, options_for_select([''], params[:school_id]), class: 'form-control school-select flex-1' %>
</div>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3', 'data-disable-with': '搜索中...') %>
<input type="reset" class="btn btn-secondary clear-btn" value="清空"/>
<% end %>
<%= javascript_void_link '导出', class: 'btn btn-outline-primary export-action', 'data-url': export_admins_user_statistics_path(format: :xlsx) %>
</div>
<div class="box user-statistic-list-container">
<%= render partial: 'admins/user_statistics/shared/list', locals: { users: @users } %>
</div>

@ -0,0 +1 @@
$('.user-statistic-list-container').html("<%= j( render partial: 'admins/user_statistics/shared/list', locals: { users: @users } ) %>");

@ -0,0 +1,34 @@
<table class="table table-hover text-center user-statistic-list-table">
<thead class="thead-light">
<tr>
<th width="14%" class="text-left">姓名</th>
<th width="38%" class="text-left">单位部门</th>
<th width="12%"><%= sort_tag('学习关卡数', name: 'study_challenge_count', path: admins_user_statistics_path) %></th>
<th width="12%"><%= sort_tag('完成关卡数', name: 'finish_challenge_count', path: admins_user_statistics_path) %></th>
<th width="12%"><%= sort_tag('学习实训数', name: 'study_shixun_count', path: admins_user_statistics_path) %></th>
<th width="12%"><%= sort_tag('完成实训数', name: 'finish_shixun_count', path: admins_user_statistics_path) %></th>
</tr>
</thead>
<tbody>
<% if users.present? %>
<% users.each do |user| %>
<tr class="user-statistic-item-<%= user.id %>">
<td class="text-left">
<%= link_to "/users/#{user.login}", target: '_blank' do %>
<%= overflow_hidden_span user.real_name, width: 100 %>
<% end %>
</td>
<td class="text-left"><%= display_text [user.school_name.presence, user.department_name.presence].compact.join(' - ') %></td>
<td><%= user.display_extra_data(:study_challenge_count) %></td>
<td><%= user.display_extra_data(:finish_challenge_count) %></td>
<td><%= user.display_extra_data(:study_shixun_count) %></td>
<td><%= user.display_extra_data(:finish_shixun_count) %></td>
</tr>
<% end %>
<% else %>
<%= render 'admins/shared/no_data_for_table' %>
<% end %>
</tbody>
</table>
<%= render partial: 'admins/shared/paginate', locals: { objects: users } %>

@ -1,4 +1,5 @@
json.count @count
json.personal @personal
json.competition_teams do
json.array! @teams.each do |team|
json.extract! team, :id, :name, :invite_code

@ -6,7 +6,22 @@ json.images do
end
json.shixuns do
json.partial! 'shixuns/shixun', locals: { shixuns: @shixuns }
json.array! @shixuns do |shixun|
json.id shixun.id
json.identifier shixun.identifier
json.name shixun.name
json.status shixun.status
json.power (current_user.shixun_permission(shixun)) # 现在首页只显示已发布的实训
# REDO: 局部缓存
json.tag_name @tag_name_map&.fetch(shixun.id, nil) || shixun.tag_repertoires.first.try(:name)
json.myshixuns_count shixun.myshixuns_count
json.stu_num shixun.myshixuns_count
json.score_info shixun.averge_star
json.challenges_count shixun.challenges_count
#json.exp shixun.all_score
json.level level_to_s(shixun.trainee)
json.pic Util::FileManage.source_disk_file_url(shixun, '_weapp')
end
end
json.subjects do

@ -0,0 +1,9 @@
json.count @results.total_count
json.results do
json.array! @results.with_highlights(multiple: true) do |obj, highlights|
json.merge! obj.to_searchable_json
json.type obj.class.name.downcase
json.title highlights.delete(:name)&.join('...') || obj.searchable_title
end
end

@ -846,12 +846,16 @@ Rails.application.routes.draw do
get '/auth/wechat/callback', to: 'oauth/wechat#create'
resource :bind_user, only: [:create]
resources :hot_keywords, only: [:index]
namespace :weapps do
resource :home, only: [:show]
resource :session, only: [:create]
resource :register, only: [:create]
resource :code_session, only: [:create]
resource :verify, only: [:create]
resources :searchs, only: [:index]
end
end
@ -904,7 +908,9 @@ Rails.application.routes.draw do
end
resource :import_users, only: [:create]
resource :import_course_members, only: [:create]
resources :user_statistics, only: [:index] do
get :export, on: :collection
end
resources :library_applies, only: [:index] do
member do
post :agree
@ -1006,14 +1012,18 @@ Rails.application.routes.draw do
end
end
resources :competitions, only: [:index, :destroy] do
resources :competitions, only: [:index, :destroy, :create] do
member do
post :publish
post :unpublish
post :online_switch
get :enroll_list
end
resource :competition_setting, only: [:show, :update]
collection do
post :hot_setting
end
resources :competition_settings, only: [:index, :update]
resources :enroll_lists, only: [:index]
end
end

@ -0,0 +1,25 @@
class MigrateExerciseChallengeScore < ActiveRecord::Migration[5.2]
include ExercisesHelper
def change
exercise = Exercise.find_by(id: 2734)
if exercise
exercise.exercise_users.where("start_at is not null").each do |exercise_user|
if exercise_user.end_at.nil?
calculate_score = calculate_student_score(exercise, exercise_user.user)[:total_score]
subjective_score = exercise_user.subjective_score
total_score_subjective_score = subjective_score < 0.0 ? 0.0 : subjective_score
total_score = calculate_score + total_score_subjective_score
exercise_user.update_attributes!(score:total_score,objective_score:calculate_score,end_at:exercise.end_time,commit_status:1,status:1,commit_method:3)
puts exercise_user.id
else
calculate_score = ExerciseShixunAnswer.where(user_id: exercise_user.user_id, exercise_question_id: exercise.exercise_questions.pluck(:id)).pluck(:score).sum
subjective_score = exercise_user.subjective_score
total_score_subjective_score = subjective_score < 0.0 ? 0.0 : subjective_score
total_score = calculate_score + total_score_subjective_score
exercise_user.update_attributes!(score:total_score,objective_score:calculate_score) if total_score > exercise_user.score
puts exercise_user.id
end
end
end
end
end

@ -0,0 +1,110 @@
class MigrateExerciseAnswerScore < ActiveRecord::Migration[5.2]
def challenge_path(path)
cha_path = path.present? ? path.split("") : []
cha_path.reject(&:blank?)[0].try(:strip)
end
# 版本库文件内容,带转码
def git_fle_content(repo_path, path)
begin
Rails.logger.info("git file content: repo_path is #{repo_path}, path is #{path}")
content = GitService.file_content(repo_path: repo_path, path: path)
Rails.logger.info("git file content: content is #{content}")
decode_content = nil
if content.present?
content = content["content"] #6.24 -hs 这个为新增,因为当实训题里含有选择题时,这里会报错,undefined method `[]' for nil:NilClass
content = Base64.decode64(content)
cd = CharDet.detect(content)
Rails.logger.info "encoding: #{cd['encoding']} confidence: #{cd['confidence']}"
# 字符编码问题GB18030编码识别率不行
decode_content =
if cd["encoding"] == 'GB18030' && cd['confidence'] > 0.8
content.encode('UTF-8', 'GBK', {:invalid => :replace, :undef => :replace, :replace => ' '})
else
content.force_encoding('UTF-8')
end
end
decode_content
rescue Exception => e
Rails.logger.error(e.message)
raise Educoder::TipException.new("文档内容获取异常")
end
end
def calculate_student_score(exercise,user)
score5 = 0.0 #实训题
exercise_questions = exercise.exercise_questions.includes(:exercise_standard_answers,:exercise_shixun_challenges)
exercise_questions.each do |q|
if q.question_type == 5
q.exercise_shixun_challenges.each do |exercise_cha|
game = Game.user_games(user.id,exercise_cha.challenge_id)&.first #当前用户的关卡
if game.present?
exercise_cha_score = 0.0
answer_status = 0
# if game.status == 2 && game.final_score >= 0
if game.final_score > 0 && game.end_time < exercise.end_time
exercise_cha_score = game.real_score(exercise_cha.question_score)
# exercise_cha_score = exercise_cha.question_score #每一关卡的得分
answer_status = 1
end
ex_shixun_answer_content = exercise_cha.exercise_shixun_answers.where(user_id:user.id,exercise_question_id:q.id)
code = nil
if exercise_cha.challenge&.path.present?
cha_path = challenge_path(exercise_cha.challenge&.path)
game_challenge = game.game_codes.search_challenge_path(cha_path)&.first
if game_challenge.present?
game_code = game_challenge
code = game_code.try(:new_code)
else
begin
code = git_fle_content(game.myshixun.repo_path,cha_path)
rescue
code = ""
end
end
end
if ex_shixun_answer_content.blank? #把关卡的答案存入试卷的实训里
### Todo 实训题的_shixun_details里的代码是不是直接从这里取出就可以了涉及到code的多个版本库的修改
sx_option = {
:exercise_question_id => q.id,
:exercise_shixun_challenge_id => exercise_cha.id,
:user_id => user.id,
:score => exercise_cha_score.round(1),
:answer_text => code,
:status => answer_status
}
ExerciseShixunAnswer.create!(sx_option)
else
ex_shixun_answer_content.first.update_attributes!(score:exercise_cha_score.round(1),answer_text:code,status:answer_status)
end
score5 += exercise_cha_score
else
score5 += 0.0
end
end
end
end
score5
end
def change
user_ids = [111887, 111892, 111883, 111880]
exercise = Exercise.find_by(id: 2734)
if exercise
exercise_users = exercise.exercise_users.where(user_id: user_ids)
exercise_users.each do |exercise_user|
calculate_score = calculate_student_score(exercise, exercise_user.user)
subjective_score = exercise_user.subjective_score
total_score_subjective_score = subjective_score < 0.0 ? 0.0 : subjective_score
total_score = calculate_score + total_score_subjective_score
exercise_user.update_attributes!(score:total_score,objective_score:calculate_score)
puts exercise_user.id
end
end
end
end

@ -0,0 +1,113 @@
class Migrate2734ExerciseAnswerScore < ActiveRecord::Migration[5.2]
def challenge_path(path)
cha_path = path.present? ? path.split("") : []
cha_path.reject(&:blank?)[0].try(:strip)
end
# 版本库文件内容,带转码
def git_fle_content(repo_path, path)
begin
Rails.logger.info("git file content: repo_path is #{repo_path}, path is #{path}")
content = GitService.file_content(repo_path: repo_path, path: path)
Rails.logger.info("git file content: content is #{content}")
decode_content = nil
if content.present?
content = content["content"] #6.24 -hs 这个为新增,因为当实训题里含有选择题时,这里会报错,undefined method `[]' for nil:NilClass
content = Base64.decode64(content)
cd = CharDet.detect(content)
Rails.logger.info "encoding: #{cd['encoding']} confidence: #{cd['confidence']}"
# 字符编码问题GB18030编码识别率不行
decode_content =
if cd["encoding"] == 'GB18030' && cd['confidence'] > 0.8
content.encode('UTF-8', 'GBK', {:invalid => :replace, :undef => :replace, :replace => ' '})
else
content.force_encoding('UTF-8')
end
end
decode_content
rescue Exception => e
Rails.logger.error(e.message)
raise Educoder::TipException.new("文档内容获取异常")
end
end
def calculate_student_score(exercise,user)
score5 = 0.0 #实训题
exercise_questions = exercise.exercise_questions.includes(:exercise_standard_answers,:exercise_shixun_challenges)
exercise_questions.each do |q|
if q.question_type == 5
q.exercise_shixun_challenges.each do |exercise_cha|
game = Game.user_games(user.id,exercise_cha.challenge_id)&.first #当前用户的关卡
if game.present?
exercise_cha_score = 0.0
answer_status = 0
# if game.status == 2 && game.final_score >= 0
if game.final_score > 0 && game.end_time < exercise.end_time
exercise_cha_score = game.real_score(exercise_cha.question_score)
# exercise_cha_score = exercise_cha.question_score #每一关卡的得分
answer_status = 1
end
ex_shixun_answer_content = exercise_cha.exercise_shixun_answers.where(user_id:user.id,exercise_question_id:q.id)
code = nil
if exercise_cha.challenge&.path.present?
cha_path = challenge_path(exercise_cha.challenge&.path)
game_challenge = game.game_codes.search_challenge_path(cha_path)&.first
if game_challenge.present?
game_code = game_challenge
code = game_code.try(:new_code)
else
begin
code = git_fle_content(game.myshixun.repo_path,cha_path)
rescue
code = ""
end
end
end
if ex_shixun_answer_content.blank? #把关卡的答案存入试卷的实训里
### Todo 实训题的_shixun_details里的代码是不是直接从这里取出就可以了涉及到code的多个版本库的修改
sx_option = {
:exercise_question_id => q.id,
:exercise_shixun_challenge_id => exercise_cha.id,
:user_id => user.id,
:score => exercise_cha_score.round(1),
:answer_text => code,
:status => answer_status
}
ExerciseShixunAnswer.create!(sx_option)
else
ex_shixun_answer_content.first.update_attributes!(score:exercise_cha_score.round(1),answer_text:code,status:answer_status)
end
score5 += exercise_cha_score
else
score5 += 0.0
end
end
end
end
score5
end
def change
exercise = Exercise.find_by(id: 2734)
if exercise
exercise_users = exercise.exercise_users.where("start_at is not null")
exercise_users.each do |exercise_user|
calculate_score = calculate_student_score(exercise, exercise_user.user)
subjective_score = exercise_user.subjective_score
total_score_subjective_score = subjective_score < 0.0 ? 0.0 : subjective_score
total_score = calculate_score + total_score_subjective_score
if exercise_user.end_at.nil?
exercise_user.update_attributes!(score:total_score,objective_score:calculate_score,end_at:exercise.end_time,commit_status:1,status:1,commit_method:3)
else
exercise_user.update_attributes!(score:total_score,objective_score:calculate_score)
end
puts exercise_user.id
end
end
end
end

File diff suppressed because one or more lines are too long

@ -134049,6 +134049,17 @@ $(document).on('turbolinks:load', function() {
});
}
});
$(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('重新上传');
})
}
});
$(document).on('turbolinks:load', function() {
if ($('body.admins-cooperatives-index-page').length > 0) {
// ------------ 保存链接 -----------
@ -135645,6 +135656,79 @@ $(document).on('turbolinks:load', function() {
}
})
;
$(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);
});
}
});
$(document).on('turbolinks:load', function() {
if ($('body.admins-users-edit-page, body.admins-users-update-page').length > 0) {
var initDepartmentSelect = true;

@ -47,7 +47,8 @@ export function initAxiosInterceptors(props) {
proxy="https://pre-newweb.educoder.net"
proxy="https://test-newweb.educoder.net"
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求
// 如果需要支持重复的请求考虑config里面自定义一个allowRepeat参考来控制
const requestMap = {};

@ -25,6 +25,7 @@ class PollDetailTabSecond extends Component{
axios.get(url).then((result)=>{
if(result){
this.setState({
page: page,
questions:result.data.questions,
questionsInfo:result.data.question_types
})
@ -55,7 +56,7 @@ class PollDetailTabSecond extends Component{
}
render(){
let{page,limit,questions,question_types}=this.state;
let {page, limit, questions, questionsInfo} = this.state;
return(
<div>
{
@ -160,9 +161,10 @@ class PollDetailTabSecond extends Component{
}):<NoneData></NoneData>
}
{
question_types && question_types.q_counts>limit &&
questionsInfo && questionsInfo.q_counts > limit &&
<div className="edu-txt-center mt20 mb50">
<Pagination showQuickJumper current={page} total={question_types.q_counts} pageSize={limit} onChange={this.changePage}></Pagination>
<Pagination showQuickJumper current={page} total={questionsInfo.q_counts} pageSize={limit}
onChange={this.changePage}></Pagination>
</div>
}

@ -24,7 +24,7 @@ class VNCContainer extends Component {
repositoryCode: '',
displayKey: 1,
vnc_reseting: false,
saving: false,
}
}
componentDidMount() {
@ -40,27 +40,61 @@ class VNCContainer extends Component {
getSecondDrawerWidth = () => {
return $('#game_right_contents').width() - firstDrawerWidth
}
onEditBlur = () => {
console.log('blurblur')
this.doFileUpdateRequestOnCodeMirrorBlur()
}
doFileUpdateRequestOnCodeMirrorBlur = () => {
if (!this.currentPath) {
console.error('未找到文件path')
return;
}
const { myshixun, game } = this.props
var url = `/myshixuns/${myshixun.identifier}/update_file.json`
const codeContent = window.editor_monaco.getValue()
this.setState({saving: true})
axios.post(url, {
content: codeContent,
// 评测的时候传1其它情况不用传主要是为了区分是用户自己提交还是自动提交
// evaluate: 0,
game_id : game.id,
path: this.currentPath
}
).then(res => {
this.setState({saving: false})
}).catch(e => {
this.setState({saving: false})
console.error('update_file error')
})
}
renderSecondDrawerChildren = () => {
const { readingCodeLoading, repositoryCode } = this.state;
const { readingCodeLoading, repositoryCode, saving } = this.state;
const { shixun } = this.props
const height = $(window).height() - 130
const isEditablePath = false;
return (
<Spin tip="加载中..." spinning={readingCodeLoading}>
<Spin tip={saving ? "保存中..." : "加载中..."} spinning={readingCodeLoading || saving}>
<div style={{ height: `${height}px` }}>
{/* (isEditablePath ? 'none' : 'block') */}
<div className="codemirrorBackground"
style={{ backgroundImage: `url('${notEditablePathImg}')`, display: (isEditablePath ? 'none' : 'block') }}></div>
style={{ backgroundImage: `url('${notEditablePathImg}')`, display: (shixun.code_edit_permission ? 'none' : 'block') }}></div>
<TPIMonaco
{...this.state}
codeLoading={readingCodeLoading}
repositoryCode={repositoryCode}
isEditablePath={false}
isEditablePath={shixun.code_edit_permission}
shixun={this.props.shixun}
doFileUpdateRequestOnCodeMirrorBlur={this.doFileUpdateRequestOnCodeMirrorBlur}
onEditBlur={this.onEditBlur}
></TPIMonaco>
</div>
</Spin>);
}
fetchReadRepositoryCode = (path) => {
this.currentPath = path;
const status = 1
const fetchRepoCodeUrl = `/tasks/${this.props.game.identifier}/rep_content.json?path=${path}&status=${status}`
this.setState({ readingCodeLoading: true });

@ -347,6 +347,9 @@ class TPIMonaco extends Component {
this.props.doFileUpdateRequestOnCodeMirrorBlur();
return false;
});
window.editor_monaco.onDidBlurEditorWidget(() => {
this.props.onEditBlur && this.props.onEditBlur();
})
})
// window.document.onkeydown = (e) => {

Loading…
Cancel
Save