diff --git a/app/controllers/competition_teams_controller.rb b/app/controllers/competition_teams_controller.rb index 817de9be..6ecbb9aa 100644 --- a/app/controllers/competition_teams_controller.rb +++ b/app/controllers/competition_teams_controller.rb @@ -1,7 +1,7 @@ class CompetitionTeamsController < ApplicationController include ApplicationHelper before_filter :find_team, :except => [:new, :create, :join_team, :search_non_user, :personal_enroll, :check_team_identity, :search_teacher] - before_filter :find_competition, :only => [:new, :create, :join_team, :search_non_user, :personal_enroll] + before_filter :find_competition, :only => [:new, :create, :join_team, :search_teacher, :search_non_user, :personal_enroll] before_filter :require_login skip_before_filter :verify_authenticity_token, :only => [:edit_rule] layout 'base_competition' @@ -12,7 +12,38 @@ class CompetitionTeamsController < ApplicationController @team_user = User.current end + def show + return render_404 if @competition.identifier != 'gcc-course-2019' + + @team_user_ids = @team.team_members.pluck(:user_id) + + shixuns = Shixun.where(user_id: @team_user_ids, status: 2) + shixuns = shixuns.joins('left join shixuns forked_shixuns on forked_shixuns.fork_from = shixuns.id and forked_shixuns.status = 2') + shixuns = shixuns.joins('left join myshixuns on myshixuns.shixun_id = shixuns.id and exists(select 1 from games where games.myshixun_id = myshixuns.id and games.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(:creator) + @myshixun_count_map = Myshixun.where(shixun_id: @shixuns.map(&:id)) + .where('exists(select 1 from games where games.myshixun_id = myshixuns.id and games.status = 2)') + .group('shixun_id').count + + course_ids = Course.joins('join members on members.course_id = courses.id') + .joins('join member_roles on member_roles.member_id = members.id and member_roles.role_id in (3,7,9)') + .where(members: { user_id: @team_user_ids }).pluck(:id) + courses = Course.where(id: course_ids).joins(:shixun_homework_commons) + @courses = courses.select('courses.id, courses.name, courses.members_count, count(*) shixun_homework_count') + .group('courses.id').order('shixun_homework_count desc') + + @course_myshixun_map = Myshixun.joins(student_works: :homework_common) + .where(homework_commons: { course_id: @courses.map(&:id) }) + .where('exists(select 1 from games where games.myshixun_id = myshixuns.id and games.status = 2)') + .group('homework_commons.course_id').count + end + def search_teacher + if params[:team] && params[:team] != "" + @team = @competition.competition_teams.where(:id => params[:team]).first + end + condition = "%#{params[:search].strip}%".gsub(" ","") @teachers = User.joins(:user_extensions).where("status = 1 and LOWER(concat(lastname, firstname, login, nickname)) LIKE '#{condition}' and user_extensions.identity = 0") end @@ -38,9 +69,13 @@ class CompetitionTeamsController < ApplicationController end def create + teacher_limited = @competition.teacher_enroll_mutiple_limited + member_limited = @competition.member_enroll_mutiple_limited + # 判断用户是否已创建过战队 - if @competition.competition_teams.where(:user_id => User.current.id).count > 0 && !User.current.is_teacher - @status, @message = -1, '您已创建过战队,不能重复创建' + if @competition.team_members.where(user_id: User.current.id).count > 0 && + ((User.current.is_teacher && teacher_limited) || (!User.current.is_teacher && member_limited)) + @status, @message = -1, '您已经在战队中了' return end @@ -52,6 +87,11 @@ class CompetitionTeamsController < ApplicationController is_teacher = User.current.user_extensions.identity == 0 return unless member_and_teacher_count_valid?(is_teacher) + # 检查老师多次报名限制 + return unless check_teacher_enroll_limited?(@competition, (params[:teacher_ids].presence || []).map(&:to_i) - [User.current.id]) + # 检查成员多次报名限制 + return unless check_member_enroll_limited?(@competition, (params[:member_ids].presence || []).map(&:to_i) - [User.current.id]) + ActiveRecord::Base.transaction do invite_code = generate_team_code new_team = CompetitionTeam.create(:competition_id => @competition.id, :name => params[:name], @@ -104,8 +144,12 @@ class CompetitionTeamsController < ApplicationController # 删除的成员 delete_member_ids = team_member_ids - new_member_ids @team.team_members.where(role: 2, user_id: delete_member_ids).delete_all + # 新增加的成员 - (new_member_ids - team_member_ids).each do |user_id| + ids = new_member_ids - team_member_ids + raise @message unless check_member_enroll_limited?(@competition, ids) # 有成员已经加入其他战队,并且只能一次报名 + + ids.each do |user_id| next if user_id.to_i == @team.user_id @team.team_members.create!(user_id: user_id, role: 2, competition_id: @competition.id) end @@ -117,12 +161,19 @@ class CompetitionTeamsController < ApplicationController # 删除的老师 delete_teacher_ids = teacher_ids - new_teacher_ids @team.team_members.where(role: 3, user_id: delete_teacher_ids).delete_all + # 新增加的老师 - (new_teacher_ids - teacher_ids).each do |user_id| + ids = new_teacher_ids - teacher_ids + raise @message unless check_teacher_enroll_limited?(@competition, ids) # 有老师已经加入其他战队,并且只能一次报名 + + ids.each do |user_id| next if user_id.to_i == @team.user_id @team.team_members.create!(user_id: user_id, role: 3, competition_id: @competition.id, is_teacher: true) end end + rescue => ex + @status = -1 + @message = ex.message end # @status:提示语标志(0:加入成功;1:邀请码错误;2:已经加入了其他队, 3:超过人数限制,4:已有指导老师,5:只有学生和老师身份的用户才能加入战队) @@ -133,7 +184,8 @@ class CompetitionTeamsController < ApplicationController return end - if TeamMember.where(:user_id => User.current.id, :competition_team_id => @competition.competition_teams.map(&:id), :is_teacher => 0).count > 0 + enroll_limited = (User.current.is_teacher ? @competition.teacher_enroll_mutiple_limited : @competition.member_enroll_mutiple_limited) + if enroll_limited && @competition.team_members.exists?(user_id: User.current.id) @status, @message = -1, '您已加入战队,不能重复加' return end @@ -243,4 +295,30 @@ class CompetitionTeamsController < ApplicationController true end + + def check_teacher_enroll_limited?(competition, user_ids) + teacher_limited = competition.teacher_enroll_mutiple_limited + return true unless teacher_limited && user_ids.present? + + repeat_teachers = competition.team_members.where(user_id: user_ids).includes(:user).to_a + if repeat_teachers.size > 0 + @status, @message = -1, "导师#{repeat_teachers.map{|t| t.user.show_real_name}.join(',')}已经加入其它战队了" + return false + end + + true + end + + def check_member_enroll_limited?(competition, user_ids) + member_limited = competition.member_enroll_mutiple_limited + return true unless member_limited && user_ids.present? + + repeat_members = competition.team_members.where(user_id: user_ids).includes(:user).to_a + if repeat_members.size > 0 + @status, @message = -1, "成员#{repeat_members.map{|t| t.user.show_real_name}.join(',')}已经加入其它战队了" + return false + end + + true + end end diff --git a/app/controllers/competitions_controller.rb b/app/controllers/competitions_controller.rb index c890ff6b..a868cbf0 100644 --- a/app/controllers/competitions_controller.rb +++ b/app/controllers/competitions_controller.rb @@ -4,7 +4,7 @@ class CompetitionsController < ApplicationController :edit_md_content, :update_md_content, :new_competition_stage, :new_stage_section, :update_competition_stage] before_filter :find_inform, :only => [:edit_inform, :update_inform] - before_filter :require_login, :only => [:enroll_portal] + before_filter :require_login, :only => [:enroll_portal, :publish] skip_before_filter :verify_authenticity_token, :only => [:edit_rule] layout 'base_competition' @@ -25,7 +25,7 @@ class CompetitionsController < ApplicationController end def index - @competitions = Competition.where(:status => 1).reorder("online_time desc") + @competitions = Competition.where('status = 1 or published_at is not null').reorder("published_at desc, online_time desc") respond_to do |format| format.html { render :layout => "base_edu"} format.js @@ -111,6 +111,7 @@ class CompetitionsController < ApplicationController @search_teams_count = @teams.count @team_members_count = TeamMember.where(:competition_team_id => @teams.pluck(:id)).count + @can_enroll = !(@user.is_teacher ? @competition.teacher_enroll_mutiple_limited : @competition.member_enroll_mutiple_limited) || !@competition.team_members.exists?(user_id: User.current.id) @is_enroll = CompetitionTeam.where(:id => TeamMember.where(:user_id => @user, :competition_team_id => @competition.competition_teams.map(&:id)).pluck(:competition_team_id)).reorder("created_at desc") @show_notice = (@competition.identifier == "gcc-dev-2018" || @competition.identifier == "gcc-annotation-2018") && @competition.competition_teams.joins(:team_members).where(:user_id => User.current.id).group('competition_teams.id').sum('IF(team_members.is_teacher=1, 1, 0)').values.any?(&:zero?) @@ -437,10 +438,10 @@ class CompetitionsController < ApplicationController def online_switch if @competition.present? if @competition.status - @competition.update_attributes(:status => 0) + @competition.update_attributes(status: false, published_at: nil) @btn_html = "上架" else - @competition.update_attributes(:status => 1, :online_time => Time.now) + @competition.update_attributes(status: true, online_time: Time.now, published_at: nil) @btn_html = "下架" end end @@ -580,7 +581,7 @@ class CompetitionsController < ApplicationController section_entries.destroy_all else if params[:entry_name].length < section_entries.size - section_entries[params[:entry_name].length, section_entries.size - 1].destroy_all + section_entries[params[:entry_name].length, section_entries.size - 1].each(&:destroy) end params[:entry_name].each_with_index do |name, index| @@ -600,6 +601,12 @@ class CompetitionsController < ApplicationController @competition.competition_stage_sections.where(:id => params[:section_id]).destroy_all end + def publish + return render_403 unless admin_or_business? + + @competition.update_column(:published_at, Time.now) + end + private def chart_exp_score_mo myshixuns, s_time, e_time diff --git a/app/models/competition.rb b/app/models/competition.rb index d66aac1a..96747b0b 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -32,4 +32,12 @@ class Competition < ActiveRecord::Base def member_count self.team_members.count == 0 ? 0 : self.team_members.count + 268 end + + def teacher_enroll_mutiple_limited + competition_staffs.where(category: 'teacher').first.try(:mutiple_limited) + end + + def member_enroll_mutiple_limited + competition_staffs.where(category: 'student').first.try(:mutiple_limited) + end end diff --git a/app/models/competition_staff.rb b/app/models/competition_staff.rb index 1a949460..6609cdbb 100644 --- a/app/models/competition_staff.rb +++ b/app/models/competition_staff.rb @@ -1,7 +1,7 @@ class CompetitionStaff < ActiveRecord::Base default_scope order: 'position asc' - attr_accessible :minimum, :maximum, :category, :position + attr_accessible :minimum, :maximum, :category, :position, :mutiple_limited belongs_to :competition diff --git a/app/services/zip_service.rb b/app/services/zip_service.rb index cb278e98..009fee77 100644 --- a/app/services/zip_service.rb +++ b/app/services/zip_service.rb @@ -314,6 +314,7 @@ module ZipService rename_zipfile = zip_name_refer ||= "#{Time.now.to_i.to_s}.zip" # 文件名过长 + index = 1 if rename_zipfile.size > MAX_PATH rename_zipfile = rename_zipfile[0,rename_zipfile.size-4][0,MAX_PATH-4] + rename_zipfile[-4,4] end @@ -329,7 +330,10 @@ module ZipService begin zipfile.add(rename_file, filename) rescue Exception => e - zipfile.get_output_stream('FILE_NOTICE.txt'){|os| os.write l(:label_file_exist)} + rename_file = rename_same_file(rename_file, index) + index += 1 + zipfile.add(rename_file, filename) + # zipfile.get_output_stream('FILE_NOTICE.txt'){|os| os.write l(:label_file_exist)} next end end @@ -376,4 +380,11 @@ module ZipService attach = Attachment.find_by_disk_filename(name) attach.filename end + + def rename_same_file(name, index) + basename = File.basename(name, ".*") + new_basename = basename + "_" + index.to_s + extname = File.extname(name) + new_basename + extname + end end \ No newline at end of file diff --git a/app/views/competition_teams/_competition_team_form.html.erb b/app/views/competition_teams/_competition_team_form.html.erb index 359a1d11..c68cc3b2 100644 --- a/app/views/competition_teams/_competition_team_form.html.erb +++ b/app/views/competition_teams/_competition_team_form.html.erb @@ -24,13 +24,12 @@
请选择指导老师,允许修改
<% @teachers.each do |teacher| %> -+ 战队详情 + <%= link_to '返回', enroll_competition_path(@competition), class: 'color-grey-9 fr' %> +
+实训项目
+创建者 | +名称 | +学习人数 | +fork版的学习人数 | +有效作品数 | +经验值 | +
---|---|---|---|---|---|
<%= shixun.creator.show_real_name %> | ++ <%= link_to shixun_path(shixun), target: '_blank' do %> + <%= shixun.name %> + <% end %> + <% if shixun.fork_from.blank? %> + 原创 + <% end %> + | +<%= shixun.myshixuns_count.to_i.zero? ? '--' : shixun.myshixuns_count.to_i %> | +<%= shixun['forked_myshixun_count'].to_i.zero? ? '--' : shixun['forked_myshixun_count'].to_i %> | +<%= @myshixun_count_map.fetch(shixun.id, '--') %> | +-- | +
合计 | +<%= @shixuns.size %> | +<%= total_myshixun_count %> | +<%= total_forked_myshixun_count %> | +<%= @myshixun_count_map.values.reduce(:+) %> | +-- | +
翻转课堂
+创建者 | +名称 | +学生数量 | +发布的实训作业数量 | +有效作品数 | +经验值 | +
---|---|---|---|---|---|
<%= course.teachers.where(user_id: @team_user_ids).first.user.show_real_name %> | ++ <%= course.name %> + | +<%= course.members_count %> | +<%= course['shixun_homework_count'].presence || '--' %> | +<%= @course_myshixun_map.fetch(course.id, '--') %> | +-- | +
合计 | +<%= @courses.size %> | +<%= total_members_count %> | +<%= total_shixun_homework_count %> | +<%= @course_myshixun_map.values.reduce(:+) %> | +-- | +
<%= @competition.name %>
- <% if @minimum_staff > 1 %> + <% if @maximum_staff > 1 %><%= @competition.sub_title %>
<% unless User.current.logged? %> <%= link_to "创建战队", signin_path, :remote => true, :class => "enroll-in-b enroll-in-b-green fr" %> <% else %> <% unless @competition.enroll_end_time.present? && @competition.enroll_end_time < Time.now %> - <% if @is_enroll.present? && !@user.is_teacher %> + <% if !@can_enroll %> 创建战队 <% else %> 创建战队 @@ -23,7 +23,7 @@ <%= link_to "加入战队", signin_path, :remote => true, :class => "enroll-in-b fr" %> <% else %> <% unless @competition.enroll_end_time.present? && @competition.enroll_end_time < Time.now %> - <% if @is_enroll.present? && !@user.is_teacher %> + <% if !@can_enroll %> 加入战队 <% else %> 加入战队 @@ -40,7 +40,7 @@ <% else %>
<% unless @competition.enroll_end_time.present? && @competition.enroll_end_time < Time.now %> - <% if @is_enroll.present? %> + <% if !@can_enroll %> 立即报名 <% else %> <%= link_to "立即报名", personal_enroll_competition_teams_path(:com_id => @competition.id), :remote => true, :class => "enroll-in-b enroll-in-b-green fr" %> @@ -97,6 +97,10 @@ 退出 <% end %> <% end %> + + <% if @competition.identifier == 'gcc-course-2019' %> + <%= link_to '战队详情', competition_team_path(id: team.id), class: 'fl mt13 ml10' %> + <% end %> <% end %>
- <%= link_to @shixun.owner.try(:show_real_name), user_path(@shixun.owner), :class => "color-grey3 fl", :target => "_blank" %> - / + <%#= link_to @shixun.owner.try(:show_real_name), user_path(@shixun.owner), :class => "color-grey3 fl", :target => "_blank" %> + <%= link_to @shixun.name, shixun_path(@shixun), :class => "edu-info-dark fl task-hide",:style=>"max-width:300px", :target => "_blank" %>
<% if User.current.has_teacher_role(@homework.course) || User.current.admin? || @work.user == User.current %> diff --git a/config/routes.rb b/config/routes.rb index 2bc0038c..c9d01c3b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -260,6 +260,7 @@ RedmineApp::Application.routes.draw do ## oauth相关 get 'send_message' get 'export_chart_score' post 'competition_images' + post 'publish' end collection do post 'new_competition' diff --git a/db/migrate/20190704063532_add_multiple_limited_to_competition_staffs.rb b/db/migrate/20190704063532_add_multiple_limited_to_competition_staffs.rb new file mode 100644 index 00000000..a4b53c96 --- /dev/null +++ b/db/migrate/20190704063532_add_multiple_limited_to_competition_staffs.rb @@ -0,0 +1,5 @@ +class AddMultipleLimitedToCompetitionStaffs < ActiveRecord::Migration + def change + add_column :competition_staffs, :mutiple_limited, :boolean, default: false + end +end diff --git a/db/migrate/20190705021338_add_published_at_to_competitions.rb b/db/migrate/20190705021338_add_published_at_to_competitions.rb new file mode 100644 index 00000000..a3420621 --- /dev/null +++ b/db/migrate/20190705021338_add_published_at_to_competitions.rb @@ -0,0 +1,5 @@ +class AddPublishedAtToCompetitions < ActiveRecord::Migration + def change + add_column :competitions, :published_at, :datetime + end +end diff --git a/lib/tasks/competiton_notice_20190704.rake b/lib/tasks/competiton_notice_20190704.rake new file mode 100644 index 00000000..0dc7e6fb --- /dev/null +++ b/lib/tasks/competiton_notice_20190704.rake @@ -0,0 +1,35 @@ +#coding=utf-8 +namespace :competition_notice do + require 'simple_xlsx_reader' + + task :send_message => :environment do + puts "--------------------------------competition_notice send_message start" + attachment = Attachment.where(id: 375091).first + if attachment.present? + path = attachment.disk_directory + name = attachment.disk_filename + if name.split(".").last == "xlsx" + doc = SimpleXlsxReader.open("files/#{path}/#{name}") + sheet = doc.sheets.first + lists = sheet.rows + + lists.each_with_index do |list, index| + if index > 0 + puts "--------------------------------user_name:#{list[0]}, user_phone:#{list[1]}" + user_name = list[0] + user_phone = list[1] + if user_name.present? && user_phone.present? + begin + status = Trustie::Sms.send(mobile: user_phone, send_type:'competition_notice' , name: user_name) + rescue => e + puts "--------------------------------发送验证码出错: #{user_name}---#{user_phone}" + end + end + end + end + else + puts "--------------------------------只支持xlsx文件" + end + end + end +end diff --git a/lib/trustie/sms/sms.rb b/lib/trustie/sms/sms.rb index 7c2dad4c..03b1dbe7 100644 --- a/lib/trustie/sms/sms.rb +++ b/lib/trustie/sms/sms.rb @@ -31,6 +31,8 @@ module Trustie params['text'] = "" if send_type.nil? params['text'] = "【Edu实训】" + code + "(手机验证码)。如非本人操作,请忽略。" + elsif send_type == "competition_notice" + params['text'] = "【Edu实训】亲爱的#{name},你参与的全国绿色计算大赛2019于7月1日开始,请及时完善信息,详戳→http://opengcc.org.cn/" elsif send_type == "teacher_register" params['text'] = "【Edu实训】亲爱的#{user_name},有新的老师#{name}注册啦,请尽快处理" elsif send_type == 'competition_start' diff --git a/public/stylesheets/educoder/edu-all.css b/public/stylesheets/educoder/edu-all.css index c550bac6..13639287 100644 --- a/public/stylesheets/educoder/edu-all.css +++ b/public/stylesheets/educoder/edu-all.css @@ -711,7 +711,7 @@ li.challenge_box:last-child{ .second_course_2{min-height: 823px;} .second_course_3{min-height: 690px;} .second_course_4{min-height: 514px;} -.second_course_5{min-height: 737px;padding-top: 190px;box-sizing: border-box;position: relative} +.second_course_5{min-height: 627px;padding-top: 190px;box-sizing: border-box;position: relative} .second_course_6{min-height: 1225px;} .course_competition_panel{ margin:30px 40px 40px;border:1px solid #ABDCF1;background: #F1F8FD;justify-content: center;align-items: center;display: -webkit-flex; @@ -728,6 +728,47 @@ li.challenge_box:last-child{ width: 360px;height: 70px;background: #2CDAD4;color: #fff!important;font-size: 30px;line-height: 70px;text-align: center; margin:0px auto;display: block; } +table.tBodyScroll tbody tr td{ + padding:6px 5px;box-sizing: border-box;font-size: 16px;color: #05101A;font-weight: normal; +} +table.tBodyScroll tbody { + display:block; + max-height:420px; + overflow-y:auto; +} +.connectTag{ + display: inline-block;background: #4CACFF;border-radius: 12px;height: 24px;line-height: 24px;color: #fff; + padding:0px 12px;font-size: 14px; +} +table.tBodyScroll thead,table.tBodyScroll tfoot,table.tBodyScroll tbody tr { + display:table; + width:100%; + table-layout:fixed; +} + +table.tBodyScroll thead,table.tBodyScroll tfoot { + width:100%; +} +table.tBodyScroll tfoot tr th{ + padding:10px 5px;box-sizing: border-box;border-top: 1px solid #eaeaea;font-weight: normal!important; +} +.lastPart,.tfootLastPart{ + width: calc( 100% - 1em )!important; +} +.lastPart tr th:last-child,.tfootLastPart tr th:last-child{position: relative;} +.tfootLastPart tr th:last-child:after{ + content: '';width: 1em;height: 100%;position: absolute;right: -1em;top:0px;border-top: 1px solid #eaeaea; +} +.lastPart tr th:last-child:after{ + content: '';background: #F5F5F5;width: 1em;height: 100%;position: absolute;right: -1em;top:0px +} +table.tBodyScroll thead th{ + background: #F5F5F5;color: #656565;padding:10px 5px;box-sizing: border-box; +} +.modalTitle{display: block;padding: 0px 30px;position: relative;line-height: 20px} +.modalTitle:before{ + position: absolute;height: 100%;width: 2px;content: '';background: #4cacff;left: 0px;top: 0px; +} diff --git a/public/stylesheets/educoder/edu-main.css b/public/stylesheets/educoder/edu-main.css index 711a1f96..881f7ed9 100644 --- a/public/stylesheets/educoder/edu-main.css +++ b/public/stylesheets/educoder/edu-main.css @@ -917,4 +917,54 @@ html>body #ajax-indicator { position: fixed; } padding-bottom: 235px; min-width: 1200px; padding-top: 60px; -} \ No newline at end of file +} + +/* css3 radio */ +.radio-check { + position: relative; + height: 35px; +} +.radio-check > input { + position: absolute; + left: 0; + top: 0; + width: 14px; + height: 14px; + opacity: 0; + z-index: 100; +} +.radio-check > label { + position: absolute; + left: 20px; + line-height: 14px; + top: 0px; +} +.radio-check > label:before { + content: ''; + position: absolute; + left: -20px; + top: 0px; + display: inline-block; + width: 14px; + height: 14px; + border-radius: 50%; + border: 1px solid #ddd; +} +.radio-check > label:after { + content: ''; + position: absolute; + left: -20px; + top: 0px; + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + margin-top: 4px; + margin-left: 4px; +} +.radio-check input[type='checkbox']:checked + label:before { + border-color: #29BD8B; +} +.radio-check input[type='checkbox']:checked + label:after { + background: #29BD8B; +}