多媒体实训地址

dev_daiao
daiao 5 years ago
commit 30eb0dc139

@ -0,0 +1,2 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.

@ -0,0 +1,2 @@
// Place all the behaviors and hooks related to the matching controller here.
// All this logic will automatically be available in application.js.

@ -0,0 +1,3 @@
// Place all the styles related to the forums controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/

@ -38,6 +38,7 @@ class ApplicationController < ActionController::Base
def user_course_identity def user_course_identity
@user_course_identity = current_user.course_identity(@course) @user_course_identity = current_user.course_identity(@course)
if @user_course_identity > Course::STUDENT && @course.is_public == 0 if @user_course_identity > Course::STUDENT && @course.is_public == 0
tip_exception(401, "..") unless User.current.logged?
tip_exception(409, "您没有权限进入") tip_exception(409, "您没有权限进入")
end end
uid_logger("###############user_course_identity:#{@user_course_identity}") uid_logger("###############user_course_identity:#{@user_course_identity}")
@ -582,4 +583,8 @@ class ApplicationController < ActionController::Base
def render_parameter_missing def render_parameter_missing
render json: { status: -1, message: '参数缺失' } render json: { status: -1, message: '参数缺失' }
end end
def set_export_cookies
cookies[:fileDownload] = true
end
end end

@ -1,7 +1,7 @@
module PaginateHelper module PaginateHelper
def paginate(objs, **opts) def paginate(objs, **opts)
page = params[:page].to_i <= 0 ? 1 : params[:page].to_i page = params[:page].to_i <= 0 ? 1 : params[:page].to_i
per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : opts[:per_page] || 20 per_page = params[:per_page].to_i > 0 && params[:per_page].to_i < 50 ? params[:per_page].to_i : opts[:per_page] || 20
Kaminari.paginate_array(objs).page(page).per(per_page) Kaminari.paginate_array(objs).page(page).per(per_page)
end end

@ -1027,10 +1027,13 @@ class CoursesController < ApplicationController
tip_exception(403,"无权限操作") tip_exception(403,"无权限操作")
elsif @all_members.size == 0 elsif @all_members.size == 0
normal_status(-1,"课堂暂时没有学生") normal_status(-1,"课堂暂时没有学生")
elsif params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else else
set_export_cookies
member_to_xlsx(@course, @all_members, @c_homeworks, @c_exercises, @c_tasks) member_to_xlsx(@course, @all_members, @c_homeworks, @c_exercises, @c_tasks)
filename_ = "#{current_user.real_name}_#{@course.name}_全部成绩" filename_ = "#{current_user.real_name}_#{@course.name}_全部成绩_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
render xlsx: "#{format_sheet_name filename_.strip.first(30)}",template: "courses/export_member_scores_excel.xlsx.axlsx", render xlsx: "#{format_sheet_name filename_.strip}",template: "courses/export_member_scores_excel.xlsx.axlsx",
locals: {course_info:@course_info, activity_level:@user_activity_level, locals: {course_info:@course_info, activity_level:@user_activity_level,
course_scores:@course_user_scores,shixun_works:@shixun_work_arrays, course_scores:@course_user_scores,shixun_works:@shixun_work_arrays,
common_works:@common_work_arrays,group_works:@group_work_arrays,task_works:@task_work_arrays, common_works:@common_work_arrays,group_works:@group_work_arrays,task_works:@task_work_arrays,

@ -1256,13 +1256,16 @@ class ExercisesController < ApplicationController
normal_status(-1,"试卷未发布") normal_status(-1,"试卷未发布")
elsif (@exercise_users_size == 0) || ( @export_ex_users&.exercise_user_committed.size == 0) elsif (@exercise_users_size == 0) || ( @export_ex_users&.exercise_user_committed.size == 0)
normal_status(-1,"暂无用户提交") normal_status(-1,"暂无用户提交")
elsif params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else else
respond_to do |format| respond_to do |format|
format.xlsx{ format.xlsx{
set_export_cookies
get_export_users(@exercise,@course,@export_ex_users) get_export_users(@exercise,@course,@export_ex_users)
exercise_export_name_ = exercise_export_name_ =
"#{current_user.real_name}_#{@course.name}_#{@exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}" "#{current_user.real_name}_#{@course.name}_#{@exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
render xlsx: "#{exercise_export_name_.strip.first(30)}",template: "exercises/exercise_lists.xlsx.axlsx",locals: {table_columns:@table_columns,exercise_users:@user_columns} render xlsx: "#{exercise_export_name_.strip}",template: "exercises/exercise_lists.xlsx.axlsx",locals: {table_columns:@table_columns,exercise_users:@user_columns}
} }
end end
end end
@ -1281,7 +1284,12 @@ class ExercisesController < ApplicationController
@exercise_questions = @exercise.exercise_questions.includes(:exercise_choices).order("question_number ASC") @exercise_questions = @exercise.exercise_questions.includes(:exercise_choices).order("question_number ASC")
filename_ = "#{@exercise.user.real_name}_#{@course.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.pdf" filename_ = "#{@exercise.user.real_name}_#{@course.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.pdf"
stylesheets = "#{Rails.root}/app/templates/exercise_export/exercise_export.css" stylesheets = "#{Rails.root}/app/templates/exercise_export/exercise_export.css"
render pdf: 'exercise_export/blank_exercise', filename: filename_, stylesheets: stylesheets if params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else
set_export_cookies
render pdf: 'exercise_export/blank_exercise', filename: filename_, stylesheets: stylesheets
end
end end
#空白试卷预览页面,仅供测试使用,无其他任何用途 #空白试卷预览页面,仅供测试使用,无其他任何用途

@ -0,0 +1,2 @@
class ForumsController < ApplicationController
end

@ -116,10 +116,11 @@ class GamesController < ApplicationController
@qrcode_str = Base64.encode64( qr.to_img.resize(400,400).to_s ) @qrcode_str = Base64.encode64( qr.to_img.resize(400,400).to_s )
else else
@type = ""
#conv = Iconv.new("GBK", "utf-8") #conv = Iconv.new("GBK", "utf-8")
@game_challenge = @game.challenge @game_challenge = @game.challenge
type = @game_challenge.show_type type = @game_challenge.show_type
@type = shixun_show_type type
workspace_path = @game.try(:picture_path) workspace_path = @game.try(:picture_path)
@answer_path = "#{Rails.root}/#{workspace_path}/#{@game_challenge.expect_picture_path}" @answer_path = "#{Rails.root}/#{workspace_path}/#{@game_challenge.expect_picture_path}"
@user_path = "#{Rails.root}/#{workspace_path}/#{@game_challenge.picture_path}" @user_path = "#{Rails.root}/#{workspace_path}/#{@game_challenge.picture_path}"

@ -132,12 +132,14 @@ class GraduationTasksController < ApplicationController
tip_exception(403, "无权限操作") tip_exception(403, "无权限操作")
elsif complete_works == 0 elsif complete_works == 0
normal_status(-1,"暂无用户提交") normal_status(-1,"暂无用户提交")
elsif params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else else
respond_to do |format| respond_to do |format|
format.xlsx{ format.xlsx{
graduation_work_to_xlsx(@work_excel,@task,current_user) graduation_work_to_xlsx(@work_excel,@task,current_user)
task_export_name_ = "#{current_user.real_name}_#{@course.name}_#{@task.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}" task_export_name_ = "#{current_user.real_name}_#{@course.name}_#{@task.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
render xlsx: "#{task_export_name_.strip.first(30)}",template: "graduation_tasks/tasks_list.xlsx.axlsx",locals: {table_columns:@head_cells_column, task_users:@task_cells_column} render xlsx: "#{task_export_name_.strip}",template: "graduation_tasks/tasks_list.xlsx.axlsx",locals: {table_columns:@head_cells_column, task_users:@task_cells_column}
} }
end end
end end
@ -148,12 +150,17 @@ class GraduationTasksController < ApplicationController
zip_works = @work_excel.where("work_status > 0") zip_works = @work_excel.where("work_status > 0")
status = checkfileSize(zip_works) status = checkfileSize(zip_works)
if status == 0 if status == 0
respond_to do |format| if params[:export].present? && params[:export]
format.zip{ normal_status(0,"正在下载中")
zipfile = zip_homework_common @task, zip_works else
file = decode64(zipfile[0][:base64file]) respond_to do |format|
send_file "#{OUTPUT_FOLDER}/#{file}", filename: filename_for_content_disposition(file), type: 'application/zip' format.zip{
} set_export_cookies
zipfile = zip_homework_common @task, zip_works
file = decode64(zipfile[0][:base64file])
send_file "#{OUTPUT_FOLDER}/#{file}", filename: filename_for_content_disposition(file), type: 'application/zip'
}
end
end end
else else
normal_status(status,status == -2 ? "500M" : "无附件可下载") normal_status(status,status == -2 ? "500M" : "无附件可下载")

@ -271,7 +271,12 @@ class GraduationTopicsController < ApplicationController
students = course.students.joins(user: :user_extension).order("user_extensions.student_id") students = course.students.joins(user: :user_extension).order("user_extensions.student_id")
graduation_topic_to_xlsx(students,course) graduation_topic_to_xlsx(students,course)
topic_export_name_ = "#{current_user.real_name}_#{course.name}_毕设选题_#{Time.now.strftime('%Y%m%d_%H%M%S')}" topic_export_name_ = "#{current_user.real_name}_#{course.name}_毕设选题_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
render xlsx: "#{topic_export_name_.strip.first(30)}",template: "graduation_topics/export.xlsx.axlsx",locals: {table_columns:@topic_head_cells,topic_users:@topic_body_cells} if params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else
set_export_cookies
render xlsx: "#{topic_export_name_.strip}",template: "graduation_topics/export.xlsx.axlsx",locals: {table_columns:@topic_head_cells,topic_users:@topic_body_cells}
end
rescue Exception => e rescue Exception => e
uid_logger(e.message) uid_logger(e.message)
missing_template missing_template

@ -207,12 +207,15 @@ class HomeworkCommonsController < ApplicationController
tip_exception(403, "无权限操作") tip_exception(403, "无权限操作")
elsif @work_excel.blank? || @work_excel.size == 0 elsif @work_excel.blank? || @work_excel.size == 0
normal_status(-1,"暂无用户提交!") normal_status(-1,"暂无用户提交!")
elsif params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else else
respond_to do |format| respond_to do |format|
format.xlsx{ format.xlsx{
set_export_cookies
student_work_to_xlsx(@work_excel,@homework) student_work_to_xlsx(@work_excel,@homework)
exercise_export_name = "#{current_user.real_name}_#{@course.name}_#{@homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}" exercise_export_name = "#{current_user.real_name}_#{@course.name}_#{@homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
render xlsx: "#{exercise_export_name.strip.first(30)}",template: "homework_commons/works_list.xlsx.axlsx",locals: render xlsx: "#{exercise_export_name.strip}",template: "homework_commons/works_list.xlsx.axlsx",locals:
{table_columns: @work_head_cells,task_users: @work_cells_column} {table_columns: @work_head_cells,task_users: @work_cells_column}
} }
end end
@ -229,12 +232,17 @@ class HomeworkCommonsController < ApplicationController
end end
if status == 0 if status == 0
respond_to do |format| if params[:export].present? && params[:export]
format.zip{ normal_status(0,"正在下载中")
zipfile = zip_homework_common @homework, zip_works else
file = decode64(zipfile[0][:base64file]) respond_to do |format|
send_file "#{OUTPUT_FOLDER}/#{file}", filename: filename_for_content_disposition(file), type: 'application/zip' format.zip{
} set_export_cookies
zipfile = zip_homework_common @homework, zip_works
file = decode64(zipfile[0][:base64file])
send_file "#{OUTPUT_FOLDER}/#{file}", filename: filename_for_content_disposition(file), type: 'application/zip'
}
end
end end
else else
normal_status(status, status == -2 ? "500M" : "无附件可下载") normal_status(status, status == -2 ? "500M" : "无附件可下载")

@ -66,9 +66,7 @@ class MemosController < ApplicationController
# GET /memos/new # GET /memos/new
def new def new
@csrf_token = session[:_csrf_toke] ||= SecureRandom.base64(32)
@tag_list = TagRepertoire.field_for_list.order("name asc") @tag_list = TagRepertoire.field_for_list.order("name asc")
end end
# GET /memos/1/edit # GET /memos/1/edit

@ -945,12 +945,15 @@ class PollsController < ApplicationController
tip_exception(403,"无权限操作") tip_exception(403,"无权限操作")
elsif (@poll.polls_status == 1) || (@poll_export_questions.size == 0) || (@poll_commit_ids.size == 0) elsif (@poll.polls_status == 1) || (@poll_export_questions.size == 0) || (@poll_commit_ids.size == 0)
normal_status(-1,"暂无用户提交") normal_status(-1,"暂无用户提交")
elsif params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else else
respond_to do |format| respond_to do |format|
format.xlsx{ format.xlsx{
set_export_cookies
polls_export_name_ = "#{current_user.real_name}_#{@course.name}_#{@poll.polls_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}" polls_export_name_ = "#{current_user.real_name}_#{@course.name}_#{@poll.polls_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
polls_user_commit = poll_commit_result(@poll,@poll_export_questions,@poll_users,@poll_commit_ids) polls_user_commit = poll_commit_result(@poll,@poll_export_questions,@poll_users,@poll_commit_ids)
render xlsx: "#{polls_export_name_.strip.first(30)}",template: "polls/commit_result.xlsx.axlsx",locals: {polls_user_commit:polls_user_commit} render xlsx: "#{polls_export_name_.strip}",template: "polls/commit_result.xlsx.axlsx",locals: {polls_user_commit:polls_user_commit}
} }
end end
end end

@ -714,6 +714,7 @@ class ShixunsController < ApplicationController
end end
end end
rescue Exception => e rescue Exception => e
logger.info("shixun_exec error: #{e.message}")
if e.message != "ActiveRecord::RecordInvalid" if e.message != "ActiveRecord::RecordInvalid"
logger.error("##########project_fork error #{e.message}") logger.error("##########project_fork error #{e.message}")
@myshixun.destroy! @myshixun.destroy!

@ -457,8 +457,8 @@ class StudentWorksController < ApplicationController
@myself_eff = @echart_data[:efficiency_list].find { |item| item.last == @user.id } @myself_eff = @echart_data[:efficiency_list].find { |item| item.last == @user.id }
@myself_consume = @echart_data[:consume_list].find { |item| item.last == @user.id } @myself_consume = @echart_data[:consume_list].find { |item| item.last == @user.id }
filename_ = "实训报告_#{@shixun&.name}_#{@use&.real_name}" filename_ = "#{@use&.student_id}_#{@use&.real_name}_#{@shixun&.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
filename = Base64.urlsafe_encode64(filename_.strip.first(30)) filename = Base64.urlsafe_encode64(filename_.strip)
stylesheets = %w(shixun_work/shixun_work.css shared/codemirror.css) stylesheets = %w(shixun_work/shixun_work.css shared/codemirror.css)
render pdf: 'shixun_work/shixun_work', filename: filename, stylesheets: stylesheets render pdf: 'shixun_work/shixun_work', filename: filename, stylesheets: stylesheets
end end

@ -26,9 +26,22 @@ class Users::BaseController < ApplicationController
render_forbidden render_forbidden
end end
def page_value
params[:page].to_i <= 0 ? 1 : params[:page].to_i
end
def per_page_value
params[:per_page].to_i > 0 && params[:per_page].to_i < 50 ? params[:per_page].to_i : 20
end
alias_method :limit_value, :per_page_value
def offset_value
(page_value - 1) * limit_value
end
def paginate(objs, **opts) def paginate(objs, **opts)
page = params[:page].to_i <= 0 ? 1 : params[:page].to_i page = page_value
per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : 20 per_page = per_page_value
return Kaminari.paginate_array(objs).page(page).per(per_page) unless observed_logged_user? && opts[:special] return Kaminari.paginate_array(objs).page(page).per(per_page) unless observed_logged_user? && opts[:special]

@ -0,0 +1,23 @@
class Users::PrivateMessageDetailsController < Users::BaseController
before_action :private_user_resources!
after_action :update_message_status, only: [:show]
def show
messages = observed_user.private_messages.without_deleted.where(target: target_user)
@count = messages.count
@messages = messages.order(send_time: :asc).includes(sender: :user_extension)
end
private
def target_user
@_target_user ||= User.find(params[:target_id])
end
# 置为已读
def update_message_status
observed_user.private_messages.only_unread.where(target: target_user).update_all(status: 1)
end
end

@ -0,0 +1,39 @@
class Users::PrivateMessagesController < Users::BaseController
before_action :private_user_resources!
after_action :update_onclick_time!, only: [:index]
def index
@count = observed_user.private_messages.without_deleted.group(:target_id).count.count
subquery = observed_user.private_messages.without_deleted.order(send_time: :desc).to_sql
query = "SELECT subquery.*, COUNT(*) message_count FROM (#{subquery}) subquery "\
"GROUP BY subquery.target_id ORDER BY subquery.send_time desc LIMIT #{limit_value} OFFSET #{offset_value}"
@messages = PrivateMessage.select('*').from("(#{query}) AS query").includes(target: :user_extension)
end
def create
receiver = User.find_by(id: params[:target_id])
return render_error('用户未找到') if receiver.blank?
@message = PrivateMessages::CreateService.call(observed_user, receiver, create_params)
rescue PrivateMessages::CreateService::Error => ex
render_error(ex.message)
end
def destroy
message = observed_user.private_messages.without_deleted.find(params[:id])
message.destroy!
render_ok
end
private
def update_onclick_time!
current_user.onclick_time.touch(:onclick_time)
end
def create_params
params.permit(:content)
end
end

@ -0,0 +1,8 @@
class Users::RecentContactsController < Users::BaseController
before_action :private_user_resources!
def index
contacts = observed_user.recent_contacts.distinct
@contacts = contacts.order('private_messages.created_at DESC').limit(10).includes(:user_extension)
end
end

@ -0,0 +1,12 @@
class Users::UnreadMessageInfosController < Users::BaseController
before_action :private_user_resources!
def show
click_time = observed_user.click_time
unread_tiding_count = observed_user.tidings.where('created_at > ?', click_time).count
unread_message_count = observed_user.private_messages.only_unread.group(:target_id).count.count
render_ok(unread_tiding_count: unread_tiding_count, unread_message_count: unread_message_count)
end
end

@ -0,0 +1,17 @@
class UsersForPrivateMessagesController < ApplicationController
before_action :require_login, :check_auth
def index
users = User.active.where.not(id: current_user.id)
keyword = params[:keyword].to_s.strip
if keyword.blank?
@users = []
return
end
users = users.where('LOWER(concat(lastname, firstname, nickname)) LIKE ?', "%#{keyword}%")
@users = users.limit(10).includes(:user_extension)
end
end

@ -8,7 +8,13 @@ class ZipsController < ApplicationController
def shixun_report def shixun_report
service = BatchExportShixunReportService.new(@homework, @all_student_works) service = BatchExportShixunReportService.new(@homework, @all_student_works)
filename_ = filename_for_content_disposition(service.filename) filename_ = filename_for_content_disposition(service.filename)
send_file service.zip, filename: filename_, type: 'application/zip' if params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else
set_export_cookies
send_file service.zip, filename: filename_, type: 'application/zip'
end
rescue BatchExportShixunReportService::Error => ex rescue BatchExportShixunReportService::Error => ex
normal_status(-1, ex.message) normal_status(-1, ex.message)
end end
@ -18,7 +24,12 @@ class ZipsController < ApplicationController
exercises = ExportExercisesService.new(@exercise,@ex_users,@request_url) exercises = ExportExercisesService.new(@exercise,@ex_users,@request_url)
file_name_ = filename_for_content_disposition(exercises.filename) file_name_ = filename_for_content_disposition(exercises.filename)
send_file exercises.ex_zip, filename: file_name_, type: 'application/zip' if params[:export].present? && params[:export]
normal_status(0,"正在下载中")
else
set_export_cookies
send_file exercises.ex_zip, filename: file_name_, type: 'application/zip'
end
rescue Exception => e rescue Exception => e
normal_status(-1, e.message) normal_status(-1, e.message)
end end

@ -0,0 +1,9 @@
module PrivateMessageDecorator
extend ApplicationDecorator
display_time_method :send_time
def unread?
status.zero?
end
end

@ -98,7 +98,7 @@ module TidingDecorator
end end
def apply_add_schools_content def apply_add_schools_content
name = container.name name = ApplyAddSchool.find_by(id: container_id)&.name
if tiding_type == 'Apply' if tiding_type == 'Apply'
I18n.t(locale_format(tiding_type)) % name I18n.t(locale_format(tiding_type)) % name
elsif status == 2 elsif status == 2
@ -201,7 +201,7 @@ module TidingDecorator
when 'Issue' then when 'Issue' then
I18n.t(locale_format(parent_container_type)) % parent_container.subject I18n.t(locale_format(parent_container_type)) % parent_container.subject
when 'Journal' then when 'Journal' then
message = object.notes.present? ? '' + message_content_helper(parent_container.notes) : '' message = parent_container&.notes.present? ? '' + message_content_helper(parent_container.notes) : ''
I18n.t(locale_format(parent_container_type)) % message I18n.t(locale_format(parent_container_type)) % message
end end
end end
@ -331,13 +331,17 @@ module TidingDecorator
end end
def challenge_work_score_content def challenge_work_score_content
I18n.t(locale_format) % container&.comment
end
def student_works_scores_appeal_content
work = StudentWork.find_by(id: parent_container_id) work = StudentWork.find_by(id: parent_container_id)
return if work.blank? name = work&.homework_common&.name
if parent_container_type == 'StudentWork' if parent_container_type == 'StudentWork'
I18n.t(locale_format(parent_container_type, tiding_type)) % work.homework_common.try(:name) I18n.t(locale_format(parent_container_type, tiding_type)) % name
elsif parent_container_type == 'UserAppealResult' || parent_container_type == 'AppealResult' else
I18n.t(locale_format(parent_container_type, status)) % work.homework_common.try(:name) I18n.t(locale_format(parent_container_type, status)) % name
end end
end end
@ -349,7 +353,7 @@ module TidingDecorator
if tiding_type == 'System' if tiding_type == 'System'
I18n.t(locale_format(tiding_type, status), reason: extra) % container.try(:title) I18n.t(locale_format(tiding_type, status), reason: extra) % container.try(:title)
else else
I18n.t(locale_format) % container.try(:title) I18n.t(locale_format(tiding_type)) % container.try(:title)
end end
end end

@ -418,7 +418,7 @@ module ExportHelper
end end
end end
out_file_name = "#{Time.now.to_i}_#{homework_common.name}.zip" out_file_name = "作品附件_#{homework_common&.course&.name}_#{homework_common.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
out_file_name.gsub!(" ", "-") out_file_name.gsub!(" ", "-")
out_file_name.gsub!("/", "_") out_file_name.gsub!("/", "_")
out_file = find_or_pack(homework_common, homework_common.user_id, digests.sort){ out_file = find_or_pack(homework_common, homework_common.user_id, digests.sort){
@ -496,8 +496,8 @@ module ExportHelper
def make_zip_name(work, file_name="") def make_zip_name(work, file_name="")
Rails.logger.info("######################file_name: #{file_name}") Rails.logger.info("######################file_name: #{file_name}")
name = file_name === "" ? "" : (file_name[0, file_name.rindex('.')]+"_") # name = file_name === "" ? "" : (file_name[0, file_name.rindex('.')]+"_")
"#{name}#{work.user.real_name}_#{((work.user.student_id.nil?) ? "" : work.user.student_id)}" "#{work&.user&.student_id}_#{work&.user&.real_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
end end
def zipping(zip_name_refer, files_paths, output_path, is_attachment=false, not_exist_file=[]) def zipping(zip_name_refer, files_paths, output_path, is_attachment=false, not_exist_file=[])

@ -0,0 +1,2 @@
module ForumsHelper
end

@ -57,4 +57,21 @@ module GamesHelper
"编译失败,请在测试结果中查看具体的错误信息" : test_set.try(:actual_output) "编译失败,请在测试结果中查看具体的错误信息" : test_set.try(:actual_output)
end end
end end
def shixun_show_type type
case type.to_i
when 1
"image"
when 2
"apk/exe"
when 3
"txt"
when 4
"html"
when 5
"mp3"
when 6
"mp4"
end
end
end end

@ -1,2 +1,6 @@
module MemosHelper module MemosHelper
def forum_list
[{id: 5, name: "技术分享"}, {id: 3, name: "操作指南"}]
end
end end

@ -0,0 +1,2 @@
class Forum < ApplicationRecord
end

@ -0,0 +1,4 @@
class Journal < ApplicationRecord
belongs_to :user
belongs_to :issue, foreign_key: :journalized_id
end

@ -1,7 +1,9 @@
class Memo < ApplicationRecord class Memo < ApplicationRecord
include Searchable::Memo include Searchable::Memo
has_many :memo_tag_repertoires, :dependent => :destroy belongs_to :forum, touch: true
has_many :memo_tag_repertoires, dependent: :destroy
has_many :tag_repertoires, :through => :memo_tag_repertoires has_many :tag_repertoires, :through => :memo_tag_repertoires
has_many :praise_tread, as: :praise_tread_object, dependent: :destroy has_many :praise_tread, as: :praise_tread_object, dependent: :destroy

@ -1,3 +1,9 @@
class PrivateMessage < ApplicationRecord class PrivateMessage < ApplicationRecord
belongs_to :user belongs_to :user
belongs_to :target, class_name: "User"
belongs_to :sender, class_name: "User"
belongs_to :receiver, class_name: "User"
scope :without_deleted, -> { where.not(status: 2) }
scope :only_unread, -> { where(status: 0) }
end end

@ -6,4 +6,21 @@ class Tiding < ApplicationRecord
belongs_to :belong_container, polymorphic: true, optional: true belongs_to :belong_container, polymorphic: true, optional: true
has_many :attachments, as: :container has_many :attachments, as: :container
def identifier
value = nil
if Object.const_defined?(container_type)
value = container.try(:identifier)
end
if value.blank? && parent_container_type && Object.const_defined?(parent_container_type)
value = parent_container_type.try(:identifier)
end
if value.blank? && belong_container_type && Object.const_defined?(belong_container_type)
value = belong_container.try(:identifier)
end
value
end
end end

@ -54,7 +54,8 @@ class User < ApplicationRecord
has_one :onclick_time, :dependent => :destroy has_one :onclick_time, :dependent => :destroy
# 新版私信 # 新版私信
has_many :private_messages, :dependent => :destroy has_many :private_messages, dependent: :destroy
has_many :recent_contacts, through: :private_messages, source: :target
has_many :tidings, :dependent => :destroy has_many :tidings, :dependent => :destroy
has_many :games, :dependent => :destroy has_many :games, :dependent => :destroy

@ -14,7 +14,7 @@ class BatchExportShixunReportService
end end
def filename def filename
@_filename ||= "#{homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}" @_filename ||= "实训报告_#{homework&.course&.name}_#{homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
end end
def zip def zip

@ -15,8 +15,8 @@ class ExerciseUserPdfService
end end
def filename def filename
user_course = @course.course_members.find_by(user_id:@ex_user_user.id)&.course_group_name # user_course = @course.course_members.find_by(user_id:@ex_user_user.id)&.course_group_name
exercise_user_name = "#{@ex_user_user.real_name}_#{user_course.present? ? user_course : "未分班"}_#{exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M')}" exercise_user_name = "#{@ex_user_user&.student_id}_#{@ex_user_user.real_name}_#{exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M')}"
"#{exercise_user_name.strip}.pdf" "#{exercise_user_name.strip}.pdf"
end end

@ -10,7 +10,7 @@ class ExportExercisesService
end end
def filename def filename
exercise_export_name = "#{exercise.user.real_name}_#{exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}" exercise_export_name = "学生答题_#{exercise&.course&.name}_#{exercise.exercise_name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
"#{exercise_export_name.strip}.zip" "#{exercise_export_name.strip}.zip"
end end

@ -10,7 +10,7 @@ class ExportShixunReportService
end end
def filename def filename
@_filename ||= "#{homework.name}-#{work.user&.student_id}-#{work.user.real_name}.pdf".gsub(' ', '-').gsub('/', '_') @_filename ||= "#{work.user&.student_id}_#{work.user.real_name}_#{homework.name}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.pdf".gsub(' ', '-').gsub('/', '_')
end end
def prepare_binding def prepare_binding

@ -0,0 +1,35 @@
class PrivateMessages::CreateService < ApplicationService
Error = Class.new(StandardError)
attr_reader :sender, :receiver, :params
def initialize(sender, receiver, **params)
@sender = sender
@receiver = receiver
@params = params
end
def call
validate!
same_attr = { sender: sender, receiver: receiver, content: content, send_time: Time.now }
message = nil
ActiveRecord::Base.transaction do
message = sender.private_messages.create!(same_attr.merge(target: receiver, status: 1))
receiver.private_messages.create!(same_attr.merge(target: sender, status: 0))
end
message
end
private
def content
@_content ||= params[:content].to_s.strip
end
def validate!
raise Error, '内容不能为空' if content.blank?
raise Error, '内容太长' if content.size > 255
end
end

@ -15,7 +15,7 @@ class ProjectPackages::SaveService < ApplicationService
is_create = package.new_record? is_create = package.new_record?
raise Error, '类型不存在' unless ProjectPackageCategory.where(id: params[:category_id]).exists? raise Error, '类型不存在' unless ProjectPackageCategory.where(id: params[:category_id]).exists?
params[:project_package_category_id] = params[:category_id].to_i params[:project_package_category_id] = params.delete(:category_id).to_i
raise Error, '竞标截止时间不能小于当前时间' if params[:deadline_at].present? && params[:deadline_at].to_time < Time.now raise Error, '竞标截止时间不能小于当前时间' if params[:deadline_at].present? && params[:deadline_at].to_time < Time.now
@ -25,7 +25,9 @@ class ProjectPackages::SaveService < ApplicationService
end end
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
package.assign_attributes(params) columns = %i[project_package_category_id title content deadline_at
min_price max_price contact_name contact_phone]
package.assign_attributes(params.slice(*columns))
package.save! package.save!
# 处理附件 # 处理附件

@ -1,6 +1,7 @@
json.users do json.users do
json.array! @users do |user| json.array! @users do |user|
json.id user.id json.id user.id
json.login user.login
json.name user.real_name json.name user.real_name
json.student_id user&.student_id json.student_id user&.student_id
json.school_name user&.school_name json.school_name user&.school_name

@ -24,5 +24,30 @@ elsif @type == "txt"
json.contents @contents.html_safe json.contents @contents.html_safe
elsif @type =="qrcode" elsif @type =="qrcode"
json.qrcode_str @qrcode_str json.qrcode_str @qrcode_str
elsif @type == "mp3" || @type == "mp4"
if @type == "mp4"
json.orignal_file [{"file_url": "http://120.27.231.56:48080/attachments/download/378171/123.mp4"}]
json.user_file [{"file_url": "http://120.27.231.56:48080/attachments/download/378172/456.mp4"}]
json.answer_file [{"file_url": "http://120.27.231.56:48080/attachments/download/378173/789.mp4"}]
else
json.orignal_file [{"file_url": "http://120.27.231.56:48080/attachments/download/378174/58099.mp3"}]
json.user_file [{"file_url": "http://120.27.231.56:48080/attachments/download/378175/654058514.mp3"}]
json.answer_file [{"file_url": "http://120.27.231.56:48080/attachments/download/378175/654058514.mp3"}]
end
# json.orignal_file do
# json.array! @orignal_picture do |file|
# json.file_url attachment_show_users_path(:file_name => file, :path => @original_path)
# end
# end
# json.user_file do
# json.array! @user_picture do |file|
# json.file_url attachment_show_users_path(:file_name => file, :path => @user_path, :time => Time.now.to_i)
# end
# end
# json.answer_file do
# json.array! @answer_picture do |file|
# json.file_url attachment_show_users_path(:file_name => file, :path => @answer_path)
# end
# end
end end

@ -1,3 +1,3 @@
json.tag_list @tag_list json.tag_list @tag_list
json.csrf_token @csrf_token json.forums @csrf_token

@ -1,6 +1,6 @@
json.extract! tiding, :id, :status, :viewed, :user_id, :tiding_type, :container_id, :container_type, :parent_container_id, :parent_container_type json.extract! tiding, :id, :status, :viewed, :user_id, :tiding_type, :container_id, :container_type, :parent_container_id, :parent_container_type
json.content tiding.content json.content tiding.content
json.identifier tiding.try(:container).try(:identifier) rescue nil json.identifier tiding.identifier
json.time tiding.how_long_time json.time tiding.how_long_time
json.new_tiding tiding.unread?(@onclick_time) json.new_tiding tiding.unread?(@onclick_time)

@ -7,6 +7,7 @@ json.is_teacher @user.user_extension&.teacher?
json.user_identity @user.identity json.user_identity @user.identity
json.tidding_count 0 json.tidding_count 0
json.user_phone_binded @user.phone.present? json.user_phone_binded @user.phone.present?
json.phone @user.phone
json.profile_completed @user.profile_completed? json.profile_completed @user.profile_completed?
if @course if @course
json.course_identity @course_identity json.course_identity @course_identity

@ -0,0 +1,11 @@
json.count @count
json.messages do
json.array! @messages.each do |message|
json.extract! message, :id, :user_id, :receiver_id, :sender_id, :content
json.send_time message.display_send_time
json.sender do
json.partial! 'users/user_simple', user: message.sender
end
end
end

@ -0,0 +1,10 @@
json.status 0
json.message 'success'
json.private_message do
json.extract! @message, :id, :user_id, :receiver_id, :sender_id, :content
json.send_time @message.display_send_time
json.sender do
json.partial! 'users/user_simple', user: @message.sender
end
end

@ -0,0 +1,13 @@
json.count @count
json.private_messages do
json.array! @messages.each do |message|
json.extract! message, :id, :content, :message_count
json.unread message.unread?
json.send_time message.display_send_time
json.target do
json.partial! 'users/user_simple', user: message.target
end
end
end

@ -0,0 +1,2 @@
json.users @contacts, partial: 'users/user_simple', as: :user
json.count @contacts.size

@ -0,0 +1,2 @@
json.users @users, partial: 'users/user_simple', as: :user
json.count @users.size

@ -53,6 +53,11 @@ Rails.application.routes.draw do
resource :grade_records, only: [:show] resource :grade_records, only: [:show]
resource :watch, only: [:create, :destroy] resource :watch, only: [:create, :destroy]
resources :project_packages, only: [:index] resources :project_packages, only: [:index]
# 私信
resources :private_messages, only: [:index, :create, :destroy]
resources :recent_contacts, only: [:index]
resource :private_message_details, only: [:show]
resource :unread_message_info, only: [:show]
end end
@ -91,6 +96,7 @@ Rails.application.routes.draw do
end end
end end
end end
resources :users_for_private_messages, only: [:index]
resources :myshixuns, param: :identifier, shallow: true do resources :myshixuns, param: :identifier, shallow: true do
member do member do

@ -0,0 +1,7 @@
class AddIndexToUser < ActiveRecord::Migration[5.2]
def change
# add_index :users, :login, unique: true
# add_index :users, :mail, unique: true
# add_index :users, :phone, unique: true
end
end

@ -5,9 +5,9 @@
<title>EduCoder</title> <title>EduCoder</title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" type="text/css" href="/stylesheets/css/edu-common.css?1525440977"> <link rel="stylesheet" type="text/css" href="/stylesheets/css/edu-common.css?15254409771">
<link rel="stylesheet" type="text/css" href="/stylesheets/educoder/edu-main.css?1525440977"> <link rel="stylesheet" type="text/css" href="/stylesheets/educoder/edu-main.css?15254409771">
<link rel="stylesheet" type="text/css" href="/stylesheets/educoder/edu-all.css?1525440977"> <link rel="stylesheet" type="text/css" href="/stylesheets/educoder/edu-all.css?15254409771">
</head> </head>
<body style="" data-gr-c-s-loaded="true"> <body style="" data-gr-c-s-loaded="true">

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

@ -0,0 +1,490 @@
/*
* jQuery File Download Plugin v1.4.5
*
* http://www.johnculviner.com
*
* Copyright (c) 2013 - John Culviner
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
* !!!!NOTE!!!!
* You must also write a cookie in conjunction with using this plugin in the server's response headers containing the file download:
* Set-Cookie: fileDownload=true; path=/"
* !!!!NOTE!!!!
*/
(function($, window){
// i'll just put them here to get evaluated on script load
var htmlSpecialCharsRegEx = /[<>&\r\n"']/gm;
var htmlSpecialCharsPlaceHolders = {
'<': 'lt;',
'>': 'gt;',
'&': 'amp;',
'\r': "#13;",
'\n': "#10;",
'"': 'quot;',
"'": '#39;' /*single quotes just to be safe, IE8 doesn't support &apos;, so use &#39; instead */
};
$.extend({
//
//$.fileDownload('/path/to/url/', options)
// see directly below for possible 'options'
fileDownload: function (fileUrl, options) {
//provide some reasonable defaults to any unspecified options below
var settings = $.extend({
//
//Requires jQuery UI: provide a message to display to the user when the file download is being prepared before the browser's dialog appears
//
preparingMessageHtml: null,
//
//Requires jQuery UI: provide a message to display to the user when a file download fails
//
failMessageHtml: null,
//
//the stock android browser straight up doesn't support file downloads initiated by a non GET: http://code.google.com/p/android/issues/detail?id=1780
//specify a message here to display if a user tries with an android browser
//if jQuery UI is installed this will be a dialog, otherwise it will be an alert
//Set to null to disable the message and attempt to download anyway
//
androidPostUnsupportedMessageHtml: "Unfortunately your Android browser doesn't support this type of file download. Please try again with a different browser.",
//
//Requires jQuery UI: options to pass into jQuery UI Dialog
//
dialogOptions: { modal: true },
//
//a function to call while the dowload is being prepared before the browser's dialog appears
//Args:
// url - the original url attempted
//
prepareCallback: function (url) { },
//
//a function to call after a file download successfully completed
//Args:
// url - the original url attempted
//
successCallback: function (url) { },
//
//a function to call after a file download request was canceled
//Args:
// url - the original url attempted
//
abortCallback: function (url) { },
//
//a function to call after a file download failed
//Args:
// responseHtml - the html that came back in response to the file download. this won't necessarily come back depending on the browser.
// in less than IE9 a cross domain error occurs because 500+ errors cause a cross domain issue due to IE subbing out the
// server's error message with a "helpful" IE built in message
// url - the original url attempted
// error - original error cautch from exception
//
failCallback: function (responseHtml, url, error) { },
//
// the HTTP method to use. Defaults to "GET".
//
httpMethod: "GET",
//
// if specified will perform a "httpMethod" request to the specified 'fileUrl' using the specified data.
// data must be an object (which will be $.param serialized) or already a key=value param string
//
data: null,
//
//a period in milliseconds to poll to determine if a successful file download has occured or not
//
checkInterval: 100,
//
//the cookie name to indicate if a file download has occured
//
cookieName: "fileDownload",
//
//the cookie value for the above name to indicate that a file download has occured
//
cookieValue: "true",
//
//the cookie path for above name value pair
//
cookiePath: "/",
//
//if specified it will be used when attempting to clear the above name value pair
//useful for when downloads are being served on a subdomain (e.g. downloads.example.com)
//
cookieDomain: null,
//
//the title for the popup second window as a download is processing in the case of a mobile browser
//
popupWindowTitle: "Initiating file download...",
//
//Functionality to encode HTML entities for a POST, need this if data is an object with properties whose values contains strings with quotation marks.
//HTML entity encoding is done by replacing all &,<,>,',",\r,\n characters.
//Note that some browsers will POST the string htmlentity-encoded whilst others will decode it before POSTing.
//It is recommended that on the server, htmlentity decoding is done irrespective.
//
encodeHTMLEntities: true
}, options);
var deferred = new $.Deferred();
//Setup mobile browser detection: Partial credit: http://detectmobilebrowser.com/
var userAgent = (navigator.userAgent || navigator.vendor || window.opera).toLowerCase();
var isIos; //has full support of features in iOS 4.0+, uses a new window to accomplish this.
var isAndroid; //has full support of GET features in 4.0+ by using a new window. Non-GET is completely unsupported by the browser. See above for specifying a message.
var isOtherMobileBrowser; //there is no way to reliably guess here so all other mobile devices will GET and POST to the current window.
if (/ip(ad|hone|od)/.test(userAgent)) {
isIos = true;
} else if (userAgent.indexOf('android') !== -1) {
isAndroid = true;
} else {
isOtherMobileBrowser = /avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|playbook|silk|iemobile|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4));
}
var httpMethodUpper = settings.httpMethod.toUpperCase();
if (isAndroid && httpMethodUpper !== "GET" && settings.androidPostUnsupportedMessageHtml) {
//the stock android browser straight up doesn't support file downloads initiated by non GET requests: http://code.google.com/p/android/issues/detail?id=1780
if ($().dialog) {
$("<div>").html(settings.androidPostUnsupportedMessageHtml).dialog(settings.dialogOptions);
} else {
alert(settings.androidPostUnsupportedMessageHtml);
}
return deferred.reject();
}
var $preparingDialog = null;
var internalCallbacks = {
onPrepare: function (url) {
//wire up a jquery dialog to display the preparing message if specified
if (settings.preparingMessageHtml) {
$preparingDialog = $("<div>").html(settings.preparingMessageHtml).dialog(settings.dialogOptions);
} else if (settings.prepareCallback) {
settings.prepareCallback(url);
}
},
onSuccess: function (url) {
//remove the perparing message if it was specified
if ($preparingDialog) {
$preparingDialog.dialog('close');
}
settings.successCallback(url);
deferred.resolve(url);
},
onAbort: function (url) {
//remove the perparing message if it was specified
if ($preparingDialog) {
$preparingDialog.dialog('close');
};
settings.abortCallback(url);
deferred.reject(url);
},
onFail: function (responseHtml, url, error) {
//remove the perparing message if it was specified
if ($preparingDialog) {
$preparingDialog.dialog('close');
}
//wire up a jquery dialog to display the fail message if specified
if (settings.failMessageHtml) {
$("<div>").html(settings.failMessageHtml).dialog(settings.dialogOptions);
}
settings.failCallback(responseHtml, url, error);
deferred.reject(responseHtml, url);
}
};
internalCallbacks.onPrepare(fileUrl);
//make settings.data a param string if it exists and isn't already
if (settings.data !== null && typeof settings.data !== "string") {
settings.data = $.param(settings.data);
}
var $iframe,
downloadWindow,
formDoc,
$form;
if (httpMethodUpper === "GET") {
if (settings.data !== null) {
//need to merge any fileUrl params with the data object
var qsStart = fileUrl.indexOf('?');
if (qsStart !== -1) {
//we have a querystring in the url
if (fileUrl.substring(fileUrl.length - 1) !== "&") {
fileUrl = fileUrl + "&";
}
} else {
fileUrl = fileUrl + "?";
}
fileUrl = fileUrl + settings.data;
}
if (isIos || isAndroid) {
downloadWindow = window.open(fileUrl);
downloadWindow.document.title = settings.popupWindowTitle;
window.focus();
} else if (isOtherMobileBrowser) {
window.location(fileUrl);
} else {
//create a temporary iframe that is used to request the fileUrl as a GET request
$iframe = $("<iframe style='display: none' src='"+fileUrl+"'></iframe>").appendTo("body");
}
} else {
var formInnerHtml = "";
if (settings.data !== null) {
$.each(settings.data.replace(/\+/g, ' ').split("&"), function () {
var kvp = this.split("=");
//Issue: When value contains sign '=' then the kvp array does have more than 2 items. We have to join value back
var k = kvp[0];
kvp.shift();
var v = kvp.join("=");
kvp = [k, v];
var key = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[0])) : decodeURIComponent(kvp[0]);
if (key) {
var value = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[1])) : decodeURIComponent(kvp[1]);
formInnerHtml += '<input type="hidden" name="' + key + '" value="' + value + '" />';
}
});
}
if (isOtherMobileBrowser) {
$form = $("<form>").appendTo("body");
$form.hide()
.prop('method', settings.httpMethod)
.prop('action', fileUrl)
.html(formInnerHtml);
} else {
if (isIos) {
downloadWindow = window.open("about:blank");
downloadWindow.document.title = settings.popupWindowTitle;
formDoc = downloadWindow.document;
window.focus();
} else {
$iframe = $("<iframe style='display: none' src='about:blank'></iframe>").appendTo("body");
formDoc = getiframeDocument($iframe);
}
formDoc.write("<html><head></head><body><form method='" + settings.httpMethod + "' action='" + fileUrl + "'>" + formInnerHtml + "</form>" + settings.popupWindowTitle + "</body></html>");
$form = $(formDoc).find('form');
}
$form.submit();
}
//check if the file download has completed every checkInterval ms
setTimeout(checkFileDownloadComplete, settings.checkInterval);
function checkFileDownloadComplete() {
//has the cookie been written due to a file download occuring?
var cookieValue = settings.cookieValue;
if(typeof cookieValue == 'string') {
cookieValue = cookieValue.toLowerCase();
}
var lowerCaseCookie = settings.cookieName.toLowerCase() + "=" + cookieValue;
if (document.cookie.toLowerCase().indexOf(lowerCaseCookie) > -1) {
//execute specified callback
internalCallbacks.onSuccess(fileUrl);
//remove cookie
var cookieData = settings.cookieName + "=; path=" + settings.cookiePath + "; expires=" + new Date(0).toUTCString() + ";";
if (settings.cookieDomain) cookieData += " domain=" + settings.cookieDomain + ";";
document.cookie = cookieData;
//remove iframe
cleanUp(false);
return;
}
//has an error occured?
//if neither containers exist below then the file download is occuring on the current window
if (downloadWindow || $iframe) {
//has an error occured?
try {
var formDoc = downloadWindow ? downloadWindow.document : getiframeDocument($iframe);
if (formDoc && formDoc.body !== null && formDoc.body.innerHTML.length) {
var isFailure = true;
if ($form && $form.length) {
var $contents = $(formDoc.body).contents().first();
try {
if ($contents.length && $contents[0] === $form[0]) {
isFailure = false;
}
} catch (e) {
if (e && e.number == -2146828218) {
// IE 8-10 throw a permission denied after the form reloads on the "$contents[0] === $form[0]" comparison
isFailure = true;
} else {
throw e;
}
}
}
if (isFailure) {
// IE 8-10 don't always have the full content available right away, they need a litle bit to finish
setTimeout(function () {
internalCallbacks.onFail(formDoc.body.innerHTML, fileUrl);
cleanUp(true);
}, 100);
return;
}
}
}
catch (err) {
//500 error less than IE9
internalCallbacks.onFail('', fileUrl, err);
cleanUp(true);
return;
}
}
//keep checking...
setTimeout(checkFileDownloadComplete, settings.checkInterval);
}
//gets an iframes document in a cross browser compatible manner
function getiframeDocument($iframe) {
var iframeDoc = $iframe[0].contentWindow || $iframe[0].contentDocument;
if (iframeDoc.document) {
iframeDoc = iframeDoc.document;
}
return iframeDoc;
}
function cleanUp(isFailure) {
setTimeout(function() {
if (downloadWindow) {
if (isAndroid) {
downloadWindow.close();
}
if (isIos) {
if (downloadWindow.focus) {
downloadWindow.focus(); //ios safari bug doesn't allow a window to be closed unless it is focused
if (isFailure) {
downloadWindow.close();
}
}
}
}
//iframe cleanup appears to randomly cause the download to fail
//not doing it seems better than failure...
//if ($iframe) {
// $iframe.remove();
//}
}, 0);
}
function htmlSpecialCharsEntityEncode(str) {
return str.replace(htmlSpecialCharsRegEx, function(match) {
return '&' + htmlSpecialCharsPlaceHolders[match];
});
}
var promise = deferred.promise();
promise.abort = function() {
cleanUp();
$iframe.attr('src', '').html('');
internalCallbacks.onAbort(fileUrl);
};
return promise;
}
});
})(jQuery, this || window);

File diff suppressed because one or more lines are too long

@ -420,7 +420,7 @@ table.text-file{}
/*-------------------------------实训路径-------------------------------*/ /*-------------------------------实训路径-------------------------------*/
.path-head{width: 100%;height: 300px;background-image: url("/images/educoder/path.jpg");background-color: #081C4B;background-size: 100% 100%;} .path-head{width: 100%;height: 300px;background-image: url("/images/educoder/path.png");background-color: #081C4B;background-size: 100% 100%;}
.pathNavLine{position: absolute;bottom: -8px;width: 100%;} .pathNavLine{position: absolute;bottom: -8px;width: 100%;}
.path-nav li{float: left;padding: 0px 30px;height: 42px;} .path-nav li{float: left;padding: 0px 30px;height: 42px;}
.path-nav li a{color:#fff;font-size: 16px;display: block; height: 40px;} .path-nav li a{color:#fff;font-size: 16px;display: block; height: 40px;}

@ -87,7 +87,7 @@
</noscript> </noscript>
<!--用于markdown转html --> <!--用于markdown转html -->
<div id="md_div" style="display: none;"></div> <div id="md_div" style="display: none;"></div>
<div id="root" class="page -layout-v -fit widthunit" style="min-width: 1535px;"> <div id="root" class="page -layout-v -fit widthunit">
<!--<div class="d2-home">--> <!--<div class="d2-home">-->
<!--<div class="d2-home__main">--> <!--<div class="d2-home__main">-->
<!--&lt;!&ndash;<img class="d2-home__loading"&ndash;&gt;--> <!--&lt;!&ndash;<img class="d2-home__loading"&ndash;&gt;-->

@ -226,6 +226,12 @@ const Interestpage = Loadable({
loading: Loading, loading: Loading,
}) })
//众包创新
const ProjectPackages=Loadable({
loader: () => import('./modules/projectPackages/ProjectPackageIndex'),
loading: Loading,
})
class App extends Component { class App extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
@ -296,6 +302,9 @@ class App extends Component {
<Router> <Router>
<Switch> <Switch>
{/*<Route path="/login" component={LoginRegisterPage}/>*/} {/*<Route path="/login" component={LoginRegisterPage}/>*/}
{/*众包创新*/}
<Route path={"/crowdsourcings"} component={ProjectPackages}/>
{/*认证*/} {/*认证*/}
<Route path="/account" component={AccountPage}/> <Route path="/account" component={AccountPage}/>

@ -41,7 +41,7 @@ export function initAxiosInterceptors(props) {
// proxy = "http://testbdweb.trustie.net" // proxy = "http://testbdweb.trustie.net"
// proxy = "http://testbdweb.educoder.net" // proxy = "http://testbdweb.educoder.net"
// proxy = "https://testeduplus2.educoder.net" // proxy = "https://testeduplus2.educoder.net"
proxy="http://47.96.87.25:48080/" proxy="http://47.96.87.25:48080"
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求 // 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求

@ -1,4 +1,6 @@
import { bytesToSize } from 'educoder'; import { bytesToSize, getUrl2 } from 'educoder';
const $ = window.$
export function isImageExtension(fileName) { export function isImageExtension(fileName) {
return fileName ? !!(fileName.match(/.(jpg|jpeg|png|gif)$/i)) : false return fileName ? !!(fileName.match(/.(jpg|jpeg|png|gif)$/i)) : false
} }
@ -36,6 +38,25 @@ export function markdownToHTML(oldContent, selector) {
return content return content
} }
} }
function _doDownload(options) {
$.fileDownload("/api" + options.url, {
successCallback: options.successCallback,
failCallback: options.failCallback
});
}
export function downloadFile(options) {
if ($.fileDownload) {
_doDownload(options)
} else {
const _url_origin = getUrl2()
$.getScript(
`${_url_origin}/javascripts/download/jquery.fileDownload.min.js`,
(data, textStatus, jqxhr) => {
_doDownload(options)
});
}
}
export function appendFileSizeToUploadFile(item) { export function appendFileSizeToUploadFile(item) {
return `${item.title}${uploadNameSizeSeperator}${item.filesize}` return `${item.title}${uploadNameSizeSeperator}${item.filesize}`

@ -15,7 +15,8 @@ export { updatePageParams as updatePageParams } from './RouterUti
export { bytesToSize as bytesToSize } from './UnitUtil'; export { bytesToSize as bytesToSize } from './UnitUtil';
export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension } from './TextUtil' export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension,
downloadFile } from './TextUtil'
export { handleDateString, getNextHalfHourOfMoment,formatDuring } from './DateUtil' export { handleDateString, getNextHalfHourOfMoment,formatDuring } from './DateUtil'

@ -8,7 +8,7 @@ import { CNotificationHOC } from '../common/CNotificationHOC'
import { RouteHOC } from './common' import { RouteHOC } from './common'
import locale from 'antd/lib/date-picker/locale/zh_CN'; import locale from 'antd/lib/date-picker/locale/zh_CN';
import { WordsBtn, MarkdownToHtml, trigger, queryString } from 'educoder'; import { WordsBtn, MarkdownToHtml, trigger, queryString, downloadFile } from 'educoder';
import axios from 'axios'; import axios from 'axios';
import Modals from '../../modals/Modals'; import Modals from '../../modals/Modals';
import CoursesListType from '../coursesPublic/CoursesListType'; import CoursesListType from '../coursesPublic/CoursesListType';
@ -18,6 +18,8 @@ import '../css/Courses.css'
import CBreadcrumb from '../common/CBreadcrumb' import CBreadcrumb from '../common/CBreadcrumb'
import DownloadMessageysl from "../../modals/DownloadMessageysl"; import DownloadMessageysl from "../../modals/DownloadMessageysl";
import { Spin } from 'antd'
//引入对应跳转的组件 //引入对应跳转的组件
//新建分组/普通作业 //新建分组/普通作业
@ -64,6 +66,7 @@ class CommonWorkDetailIndex extends Component{
this.state = { this.state = {
DownloadType:false, DownloadType:false,
DownloadMessageval:undefined, DownloadMessageval:undefined,
donwloading:false,
} }
} }
initWorkDetailCommonState = (data) => { initWorkDetailCommonState = (data) => {
@ -103,7 +106,10 @@ class CommonWorkDetailIndex extends Component{
/// 确认是否下载 /// 确认是否下载
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url+ '&export=true').then((response) => {
if(response===undefined){
return
}
if(response.data.status&&response.data.status===-1){ if(response.data.status&&response.data.status===-1){
}else if(response.data.status&&response.data.status===-2){ }else if(response.data.status&&response.data.status===-2){
@ -123,7 +129,20 @@ class CommonWorkDetailIndex extends Component{
} }
}else { }else {
this.props.showNotification(`正在下载中`); this.props.showNotification(`正在下载中`);
window.open("/api"+url, '_blank');
this.setState({ donwloading: true })
downloadFile({
url: url,
successCallback: (url) => {
this.setState({ donwloading: false })
console.log('successCallback')
},
failCallback: (responseHtml, url) => {
this.setState({ donwloading: false })
console.log('failCallback')
}
})
// window.open("/api"+url, '_blank');
} }
}).catch((error) => { }).catch((error) => {
console.log(error) console.log(error)
@ -171,7 +190,9 @@ class CommonWorkDetailIndex extends Component{
let params = {} let params = {}
if (isListModule) { if (isListModule) {
// TODO // TODO
params =this.child._getRequestParams()!==undefined?this.child._getRequestParams():{}; if(this.child!=undefined) {
params = this.child._getRequestParams() !== undefined ? this.child._getRequestParams() : {};
}
} }
// console.log("普通作业176176176"); // console.log("普通作业176176176");
// console.log(params); // console.log(params);
@ -246,7 +267,7 @@ class CommonWorkDetailIndex extends Component{
} }
</div> </div>
{ noTab !== true && <div className="stud-class-set bor-bottom-greyE"> { noTab !== true && <div className="stud-class-set bor-bottom-greyE floatSpinParent">
<div className="mt10 clearfix edu-back-white poll_list pl5"> <div className="mt10 clearfix edu-back-white poll_list pl5">
<Link <Link
onClick={() => this.setState({moduleName: '作品列表'})} onClick={() => this.setState({moduleName: '作品列表'})}
@ -285,18 +306,30 @@ class CommonWorkDetailIndex extends Component{
padding-top: 10px; padding-top: 10px;
padding-bottom: 8px; padding-bottom: 8px;
} }
.floatSpinParent .ant-spin-nested-loading {
float: right;
}
`}</style> `}</style>
{this.props.isAdmin()? <li className="li_line drop_down fr color-blue font-16 mr8 mt20" style={{"padding":"0 20px"}}> {this.props.isAdmin()? <Spin spinning={this.state.donwloading} style={{ }}>
<li className="li_line drop_down fr color-blue font-16 mr8 mt20" style={{"padding":"0 20px"}}>
导出<i className="iconfont icon-xiajiantou font-12 ml2"></i> 导出<i className="iconfont icon-xiajiantou font-12 ml2"></i>
<ul className="drop_down_menu" style={{"right":"-34px","left":"unset","height":"auto"}}> <ul className="drop_down_menu" style={{"right":"-34px","left":"unset","height":"auto"}}>
<li><a href={"javascript:void(0)"} className="color-dark" <li>
onClick={() => this.confirmysl(exportResultUrl, exportParams)}
>导出成绩</a></li> <a href={"javascript:void(0)"} className="color-dark"
<li><a href={"javascript:void(0)"} className="color-dark" onClick={() => this.confirmysl(exportResultUrl)}
onClick={() => this.confirmysl(exportUrl, exportParams)} >导出成绩</a>
>导出作品附件</a></li>
</li>
<li>
<a href={"javascript:void(0)"} className="color-dark"
onClick={() => this.confirmysl(exportUrl)}
>导出作品附件</a>
</li>
</ul> </ul>
</li>:""} </li>
</Spin>:""}
{/* {isAdmin && <a className={"fr color-blue font-16"} href={exportUrl}></a>} {/* {isAdmin && <a className={"fr color-blue font-16"} href={exportUrl}></a>}
{isAdmin && <a className={"fr color-blue font-16"} href={exportResultUrl}>导出成绩</a>} */} {isAdmin && <a className={"fr color-blue font-16"} href={exportResultUrl}>导出成绩</a>} */}

@ -425,7 +425,8 @@ class CommonWorkList extends Component{
teacher_comment: arg_teacher_comment.length == 0 ? '' : arg_teacher_comment[0], teacher_comment: arg_teacher_comment.length == 0 ? '' : arg_teacher_comment[0],
order, order,
limit: PAGE_SIZE, limit: PAGE_SIZE,
b_order: orderMap[order] b_order: orderMap[order],
group_id:arg_course_group,
} }
} }
fetchList = () => { fetchList = () => {

@ -44,7 +44,7 @@ class TabRightComponents extends Component{
} }
/// 确认是否下载 /// 确认是否下载
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url + '?export=true' ).then((response) => {
if(response.data.status&&response.data.status===-1){ if(response.data.status&&response.data.status===-1){
}else if(response.data.status&&response.data.status===-2){ }else if(response.data.status&&response.data.status===-2){

@ -33,6 +33,21 @@ class ExerciseDisplay extends Component{
this.state = { this.state = {
exercise_questions: [], exercise_questions: [],
exercise_group_id:[],
page:1,
limit:10,
searchtext:"",
order: "end_at",
}
}
_getRequestParams() {
const { order, exercise_group_id,searchtext, page ,limit} = this.state
return {
page,
search:searchtext,
order,
limit: limit,
group_id:exercise_group_id,
} }
} }
componentDidMount = () => { componentDidMount = () => {
@ -49,6 +64,21 @@ class ExerciseDisplay extends Component{
console.log(error); console.log(error);
}); });
} }
try {
this.props.triggerRef(this);
}catch (e) {
}
}
_getRequestParams() {
const { order, exercise_group_id,searchtext, page ,limit} = this.state
return {
page,
search:searchtext,
order,
limit: limit,
group_id:exercise_group_id,
}
} }
render() { render() {
// let { question_title, question_score, question_type, question_choices, standard_answer, // let { question_title, question_score, question_type, question_choices, standard_answer,

@ -63,17 +63,46 @@ class Exercisesetting extends Component{
choice_random:true, choice_random:true,
time:0, time:0,
publish_timetype:false, publish_timetype:false,
end_timetype:false end_timetype:false,
exercise_group_id:[],
page:1,
limit:10,
searchtext:"",
order: "end_at",
} }
console.log("Exercisesetting"); console.log("Exercisesetting");
console.log("69"); console.log("69");
console.log(props); console.log(props);
} }
_getRequestParams() {
const { order, exercise_group_id,searchtext, page ,limit} = this.state
return {
page,
search:searchtext,
order,
limit: limit,
group_id:exercise_group_id,
}
}
//加载 //加载
componentDidMount=()=>{ componentDidMount=()=>{
this.getSettingInfo(); this.getSettingInfo();
// window.addEventListener('click', this.handleClick); // window.addEventListener('click', this.handleClick);
try {
this.props.triggerRef(this);
}catch (e) {
}
}
_getRequestParams() {
const { order, exercise_group_id,searchtext, page ,limit} = this.state
return {
page,
search:searchtext,
order,
limit: limit,
group_id:exercise_group_id,
}
} }
// handleClick=(e)=>{ // handleClick=(e)=>{

@ -23,15 +23,32 @@ class Exercisestatisticalresult extends Component {
exercise_group_id:[], exercise_group_id:[],
page:1, page:1,
limit:10, limit:10,
searchtext:"",
order: "end_at",
} }
} }
componentDidMount() { componentDidMount() {
let{sort,exercise_group_id,page,limit}=this.state; let{sort,exercise_group_id,page,limit}=this.state;
this.updatefun(sort,exercise_group_id,page,limit) this.updatefun(sort,exercise_group_id,page,limit);
} try {
this.props.triggerRef(this);
}catch (e) {
}
}
_getRequestParams() {
const { order, exercise_group_id,searchtext, page ,limit} = this.state
return {
page,
search:searchtext,
order,
limit: limit,
group_id:exercise_group_id,
}
}
updatefun=(sort,exercise_group_id,page,limit)=>{ updatefun=(sort,exercise_group_id,page,limit)=>{
let ExerciseId=this.props.match.params.Id; let ExerciseId=this.props.match.params.Id;
let url = `/exercises/`+ExerciseId+`/exercise_result.json`; let url = `/exercises/`+ExerciseId+`/exercise_result.json`;

@ -1213,6 +1213,11 @@ class Studentshavecompletedthelist extends Component {
// console.log("2222222222222"); // console.log("2222222222222");
// console.log(this.props.isAdmin()); // console.log(this.props.isAdmin());
// } // }
try {
this.props.triggerRef(this);
}catch (e) {
}
} }
componentWillReceiveProps = (nextProps) => { componentWillReceiveProps = (nextProps) => {
@ -1384,7 +1389,8 @@ class Studentshavecompletedthelist extends Component {
loadingstate: true, loadingstate: true,
}) })
} }
console.log(response);
console.log(1393);
thiss.Generatenewdatasy(response.data.exercise_users, response); thiss.Generatenewdatasy(response.data.exercise_users, response);
} }
}).catch((error) => { }).catch((error) => {
@ -1984,9 +1990,14 @@ class Studentshavecompletedthelist extends Component {
}).then((response) => { }).then((response) => {
// console.log("528"); // console.log("528");
// console.log(JSON.stringify(response)); // console.log(JSON.stringify(response));
if(response===undefined){
return
}
this.setState({ this.setState({
loadingstate: false, loadingstate: false,
}) })
console.log(response);
console.log(1997);
this.Generatenewdatasy(response.data.exercise_users, response); this.Generatenewdatasy(response.data.exercise_users, response);
}).catch((error) => { }).catch((error) => {
console.log(error) console.log(error)
@ -2279,22 +2290,8 @@ class Studentshavecompletedthelist extends Component {
this.Searchdatasys(this.state.order, this.state.commit_status, this.state.review, this.state.checkedValuesineinfo, this.state.searchtext, 1, this.state.limit); this.Searchdatasys(this.state.order, this.state.commit_status, this.state.review, this.state.checkedValuesineinfo, this.state.searchtext, 1, this.state.limit);
} }
} };
//搜索学生 文字输入 //搜索学生按钮输入 老师
inputSearchValues = (e) => {
// console.log(e.target.value)
if (e.target.value === "") {
this.setState({
searchtext: undefined,
})
} else {
this.setState({
searchtext: e.target.value,
})
}
}
//搜索学生按钮输入
searchValues = (value) => { searchValues = (value) => {
//点击直接搜索 //点击直接搜索
// if(value === ""){ // if(value === ""){
@ -2313,8 +2310,34 @@ class Studentshavecompletedthelist extends Component {
// console.log(value) // console.log(value)
};
_getRequestParams() {
const { order, checkedValuesineinfo,searchtext, page ,limit} = this.state
return {
page,
search:searchtext,
group_id:checkedValuesineinfo,
limit: limit,
order,
}
} }
//搜索学生 文字输入
inputSearchValues = (e) => {
// console.log(e.target.value)
if (e.target.value === "") {
this.setState({
searchtext: undefined,
})
} else {
this.setState({
searchtext: e.target.value,
})
}
};
//排序 //排序
funordersy = (e) => { funordersy = (e) => {
if (e === "end_at") { if (e === "end_at") {

@ -1,7 +1,7 @@
import React,{ Component } from "react"; import React,{ Component } from "react";
import {Checkbox,Input,Table, Pagination,Menu} from "antd"; import {Checkbox,Input,Table, Pagination,Menu} from "antd";
import {Link,NavLink} from 'react-router-dom'; import {Link,NavLink} from 'react-router-dom';
import { WordsBtn ,ActionBtn} from 'educoder'; import { WordsBtn ,ActionBtn,queryString} from 'educoder';
import CoursesListType from '../coursesPublic/CoursesListType'; import CoursesListType from '../coursesPublic/CoursesListType';
import '../css/members.css'; import '../css/members.css';
import '../css/busyWork.css'; import '../css/busyWork.css';
@ -19,7 +19,7 @@ import Ecerciseallbackagain from './Ecerciseallbackagain';
const polls_status={1:"未发布",2:"提交中",3:"已截止",4:"已结束"} const polls_status={1:"未发布",2:"提交中",3:"已截止",4:"已结束"}
const start_Value={0:"继续答题",1:"查看答题",2:"开始答题"} const start_Value={0:"继续答题",1:"查看答题",2:"开始答题"}
const qs = require('qs');
//试卷主要设置页面 //试卷主要设置页面
class Testpapersettinghomepage extends Component{ class Testpapersettinghomepage extends Component{
constructor(props) { constructor(props) {
@ -163,8 +163,17 @@ class Testpapersettinghomepage extends Component{
} }
/// 确认是否下载 /// 确认是否下载
confirmysl(url){ confirmysl(url,child){
axios.get(url).then((response) => { let params ={}
if(child!=undefined){
params =child._getRequestParams()!==undefined?child._getRequestParams():{};
}
console.log("170");
console.log(params);
axios.get(url+`?${queryString.stringify(params)}`+ '&export=true' ).then((response) => {
if(response===undefined){
return
}
if(response.data.status&&response.data.status===-1){ if(response.data.status&&response.data.status===-1){
}else if(response.data.status&&response.data.status===-2){ }else if(response.data.status&&response.data.status===-2){
@ -184,7 +193,7 @@ class Testpapersettinghomepage extends Component{
} }
}else { }else {
this.props.showNotification(`正在下载中`); this.props.showNotification(`正在下载中`);
window.open("/api"+url, '_blank'); window.open("/api"+url+`?${queryString.stringify(params)}`, '_blank');
} }
}).catch((error) => { }).catch((error) => {
console.log(error) console.log(error)
@ -218,6 +227,7 @@ class Testpapersettinghomepage extends Component{
// DownloadMessageval:undefined // DownloadMessageval:undefined
// }) // })
// } // }
bindRef = ref => { this.child = ref };
goback=()=>{ goback=()=>{
// let {datalist}=this.state; // let {datalist}=this.state;
// let courseId=this.props.match.params.coursesId; // let courseId=this.props.match.params.coursesId;
@ -236,6 +246,7 @@ class Testpapersettinghomepage extends Component{
let {tab,visible,Commonheadofthetestpaper}=this.state; let {tab,visible,Commonheadofthetestpaper}=this.state;
const isAdmin =this.props.isAdmin(); const isAdmin =this.props.isAdmin();
const isStudent = this.props.isStudent(); const isStudent = this.props.isStudent();
// TODO
return( return(
<div className="newMain clearfix "> <div className="newMain clearfix ">
@ -267,7 +278,7 @@ class Testpapersettinghomepage extends Component{
<p className="clearfix mb20 mt10"> <p className="clearfix mb20 mt10">
<a className=" btn colorgrey fl hovercolorblue " onClick={this.goback} >{this.props.coursedata.name}</a> <a className=" btn colorgrey fl hovercolorblue " onClick={this.goback} >{this.props.coursedata.name}</a>
<span className="color-grey-9 fl ml3 mr3">&gt;</span> <span className="color-grey-9 fl ml3 mr3">&gt;</span>
<a className=" btn colorgrey fl hovercolorblue " href={`/courses/${this.props.match.params.coursesId}/exercises/${Commonheadofthetestpaper&&Commonheadofthetestpaper.user_permission.left_banner_id}`} >试卷</a> <Link className=" btn colorgrey fl hovercolorblue " to={`/courses/${this.props.match.params.coursesId}/exercises/${Commonheadofthetestpaper&&Commonheadofthetestpaper.user_permission.left_banner_id}`} >试卷</Link>
<span className="color-grey-9 fl ml3 mr3">&gt;</span> <span className="color-grey-9 fl ml3 mr3">&gt;</span>
<WordsBtn className="fl">试卷详情</WordsBtn> <WordsBtn className="fl">试卷详情</WordsBtn>
</p> </p>
@ -345,7 +356,7 @@ class Testpapersettinghomepage extends Component{
{isAdmin === true? <li className="li_line drop_down fr color-blue font-15" style={{"paddingLeft":"0px"}}> {isAdmin === true? <li className="li_line drop_down fr color-blue font-15" style={{"paddingLeft":"0px"}}>
导出<i className="iconfont icon-xiajiantou font-12 ml2"></i> 导出<i className="iconfont icon-xiajiantou font-12 ml2"></i>
<ul className="drop_down_menu" style={{"right":"-34px","left":"unset","height":"auto"}}> <ul className="drop_down_menu" style={{"right":"-34px","left":"unset","height":"auto"}}>
<li><a onClick={()=>this.confirmysl(`/exercises/${this.props.match.params.Id}/exercise_lists.xlsx`)}>学生成绩</a></li> <li><a onClick={()=>this.confirmysl(`/exercises/${this.props.match.params.Id}/exercise_lists.xlsx`,this.child)}>学生成绩</a></li>
{/*<li><a onClick={()=>this.confirmysl(`/exercises/${this.props.match.params.Id}/export_exercise`)} >空白试卷</a></li>*/} {/*<li><a onClick={()=>this.confirmysl(`/exercises/${this.props.match.params.Id}/export_exercise`)} >空白试卷</a></li>*/}
{/*<li><a onClick={()=>this.confirmysl(`/zip/export_exercises?exercise_id=${this.props.match.params.Id}${this.state.groupyslsval===null||this.state.groupyslsval===undefined?null:this.state.groupyslsval}`)}>学生答题试卷</a></li>*/} {/*<li><a onClick={()=>this.confirmysl(`/zip/export_exercises?exercise_id=${this.props.match.params.Id}${this.state.groupyslsval===null||this.state.groupyslsval===undefined?null:this.state.groupyslsval}`)}>学生答题试卷</a></li>*/}
</ul> </ul>
@ -400,22 +411,22 @@ class Testpapersettinghomepage extends Component{
/> />
{ {
// 教师列表 // 教师列表
parseInt(tab[0])==0 && <Studentshavecompletedthelist {...this.props} {...this.state} setcourse_groupysls={(value)=>this.setcourse_groupysls(value)} current_status = {this.state.current_status}></Studentshavecompletedthelist> parseInt(tab[0])==0 && <Studentshavecompletedthelist {...this.props} {...this.state} triggerRef={this.bindRef} setcourse_groupysls={(value)=>this.setcourse_groupysls(value)} current_status = {this.state.current_status}></Studentshavecompletedthelist>
} }
{/*统计结果*/} {/*统计结果*/}
{ {
parseInt(tab[0])==1 && <Exercisestatisticalresult {...this.props} {...this.state}></Exercisestatisticalresult> parseInt(tab[0])==1 && <Exercisestatisticalresult {...this.props} {...this.state} triggerRef={this.bindRef}></Exercisestatisticalresult>
} }
{ {
parseInt(tab[0])==2 && <ExerciseDisplay {...this.props} {...this.state}></ExerciseDisplay> parseInt(tab[0])==2 && <ExerciseDisplay {...this.props} {...this.state} triggerRef={this.bindRef}></ExerciseDisplay>
} }
{ {
parseInt(tab[0])==3 && <WrappedExercisesetting {...this.props} {...this.state} ></WrappedExercisesetting> parseInt(tab[0])==3 && <WrappedExercisesetting {...this.props} {...this.state} triggerRef={this.bindRef}></WrappedExercisesetting>
} }
</div> </div>
</div> </div>

@ -896,7 +896,7 @@ class GraduationTaskssettingapp extends Component{
} }
/// 确认是否下载 /// 确认是否下载
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url + '?export=true' ).then((response) => {
if(response === undefined){ if(response === undefined){
return return
} }

@ -645,7 +645,7 @@ class GraduationTaskssettinglist extends Component{
b_order:b_order, b_order:b_order,
search:search, search:search,
} }
axios.get(url,{ axios.get(url + '?export=true',{
params params
}).then((response) => { }).then((response) => {
if(response === undefined){ if(response === undefined){

@ -199,7 +199,7 @@ class GraduationTasksquestions extends Component{
/// 确认是否下载 /// 确认是否下载
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url + '?export=true').then((response) => {
if(response === undefined){ if(response === undefined){
return return
} }

@ -322,7 +322,7 @@ onBoardsNew=()=>{
} }
/// 确认是否下载 /// 确认是否下载
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url + '?export=true').then((response) => {
if(response.data.status&&response.data.status===-1){ if(response.data.status&&response.data.status===-1){
}else if(response.data.status&&response.data.status===-2){ }else if(response.data.status&&response.data.status===-2){

@ -127,7 +127,7 @@ class studentsList extends Component{
} }
/// 确认是否下载 /// 确认是否下载
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url + '&export=true').then((response) => {
if(response === undefined){ if(response === undefined){
return return
} }

@ -75,7 +75,7 @@ class PollDetailIndex extends Component{
/// 确认是否下载 /// 确认是否下载
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url + '?export=true' ).then((response) => {
if(response.data.status&&response.data.status===-1){ if(response.data.status&&response.data.status===-1){
}else if(response.data.status&&response.data.status===-2){ }else if(response.data.status&&response.data.status===-2){

@ -2214,7 +2214,7 @@ class Listofworksstudentone extends Component {
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url + '&export=true').then((response) => {
if(response === undefined){ if(response === undefined){
return return
} }

@ -539,7 +539,7 @@ class ShixunStudentWork extends Component {
} }
confirmysl(url){ confirmysl(url){
axios.get(url).then((response) => { axios.get(url + '?export=true').then((response) => {
if(response === undefined){ if(response === undefined){
return return
} }

@ -1766,12 +1766,12 @@ class Trainingjobsetting extends Component {
daochushixunbaogao=()=>{ daochushixunbaogao=()=>{
let url =`/zip/shixun_report?homework_common_id=${this.props.match.params.homeworkid}`; let url =`/zip/shixun_report?homework_common_id=${this.props.match.params.homeworkid}`;
this.confirmysl(url); this.confirmysl(url + '&export=true');
} }
daochuzuoye =() =>{ daochuzuoye =() =>{
let url = `/homework_commons/${this.props.match.params.homeworkid}/works_list.xlsx`; let url = `/homework_commons/${this.props.match.params.homeworkid}/works_list.xlsx`;
this.confirmysl(url); this.confirmysl(url + '?export=true');
} }
confirmysl(url){ confirmysl(url){

@ -339,12 +339,12 @@ class Workquestionandanswer extends Component {
daochushixunbaogao=()=>{ daochushixunbaogao=()=>{
let url =`/zip/shixun_report?homework_common_id=${this.props.match.params.homeworkid}`; let url =`/zip/shixun_report?homework_common_id=${this.props.match.params.homeworkid}`;
this.confirmysl(url); this.confirmysl(url + '&export=true');
} }
daochuzuoye =() =>{ daochuzuoye =() =>{
let url = `/homework_commons/${this.props.match.params.homeworkid}/works_list.xlsx`; let url = `/homework_commons/${this.props.match.params.homeworkid}/works_list.xlsx`;
this.confirmysl(url); this.confirmysl(url + '?export=true');
} }
confirmysl(url){ confirmysl(url){

@ -18,7 +18,7 @@ class DownloadMessage extends Component {
setDownload=()=>{ setDownload=()=>{
this.modalCancel(); this.modalCancel();
window.open(`/users/${this.props.user.login}/private_messages`) window.open(`/users/${this.props.user.login}/message_detail?user_id=1`)
} }
modalCancel = () => { modalCancel = () => {
this.setState({ this.setState({

@ -11,7 +11,7 @@ class DownloadMessageysl extends Component {
setDownload=()=>{ setDownload=()=>{
this.props.modalCancel(); this.props.modalCancel();
window.open(`/users/${this.props.user.login}/private_messages`) window.open(`/users/${this.props.user.login}/message_detail?user_id=1`)
} }
render() { render() {

@ -500,7 +500,9 @@ class DetailCardsEditAndAdd extends Component{
<div className="clearfix edu-txt-center lineh-40 bor-bottom-greyE" key={key}> <div className="clearfix edu-txt-center lineh-40 bor-bottom-greyE" key={key}>
<li className="fl with40"> <li className="fl with40">
<Checkbox <Checkbox
id={"shixun_input_"+item.shixun_id} value={item.shixun_id} id={"shixun_input_"+item.shixun_id}
value={item.shixun_id}
key={item.shixun_id}
className="fl task-hide edu-txt-left" className="fl task-hide edu-txt-left"
style={{"width":"298px"}} style={{"width":"298px"}}
name="shixun_homework[]" name="shixun_homework[]"

@ -533,7 +533,9 @@ class DetailCardsEditAndEdit extends Component{
<div className="clearfix edu-txt-center lineh-40 bor-bottom-greyE" key={key}> <div className="clearfix edu-txt-center lineh-40 bor-bottom-greyE" key={key}>
<li className="fl with40"> <li className="fl with40">
<Checkbox <Checkbox
id={"shixun_input_"+item.shixun_id} value={item.shixun_id} id={"shixun_input_"+item.shixun_id}
value={item.shixun_id}
key={item.shixun_id}
className="fl task-hide edu-txt-left" className="fl task-hide edu-txt-left"
style={{"width":"298px"}} style={{"width":"298px"}}
name="shixun_homework[]" name="shixun_homework[]"

@ -0,0 +1,337 @@
import React, {Component} from 'react';
import {Input, Select, Radio, Checkbox, Popconfirm, message, Modal} from 'antd';
import {BrowserRouter as Router, Route, Link, Switch} from "react-router-dom";
import { getImageUrl, toPath, getUrl } from 'educoder';
require('codemirror/lib/codemirror.css');
let origin = getUrl();
let path = getUrl("/editormd/lib/")
const $ = window.$;
let timeout;
let currentValue;
const Option = Select.Option;
const RadioGroup = Radio.Group;
// 保存数据
function md_add_data(k,mdu,d){
window.sessionStorage.setItem(k+mdu,d);
}
// 清空保存的数据
function md_clear_data(k,mdu,id){
window.sessionStorage.removeItem(k+mdu);
var id1 = "#e_tip_"+id;
var id2 = "#e_tips_"+id;
if(k == 'content'){
$(id2).html(" ");
}else{
$(id1).html(" ");
}
}
window.md_clear_data = md_clear_data
// editor 存在了jquery对象上应用不需要自己写md_rec_data方法了
function md_rec_data(k, mdu, id) {
if (window.sessionStorage.getItem(k + mdu) !== null) {
var editor = $("#e_tips_" + id).data('editor');
editor.setValue(window.sessionStorage.getItem(k + mdu));
// debugger;
// /shixuns/b5hjq9zm/challenges/3977/tab=3 setValue可能导致editor样式问题
md_clear_data(k, mdu, id);
}
}
window.md_rec_data = md_rec_data;
function md_elocalStorage(editor,mdu,id){
if (window.sessionStorage){
var oc = window.sessionStorage.getItem('content'+mdu);
if(oc !== null ){
console.log("#e_tips_"+id)
$("#e_tips_"+id).data('editor', editor);
var h = '您上次有已保存的数据,是否<a style="cursor: pointer;" class="link-color-blue" onclick="md_rec_data(\'content\',\''+ mdu + '\',\'' + id + '\')">恢复</a> ? / <a style="cursor: pointer;" class="link-color-blue" onclick="md_clear_data(\'content\',\''+ mdu + '\',\'' + id + '\')">不恢复</a>';
$("#e_tips_"+id).html(h);
}
setInterval(function() {
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var s = d.getSeconds();
h = h < 10 ? '0' + h : h;
m = m < 10 ? '0' + m : m;
s = s < 10 ? '0' + s : s;
if(editor.getValue().trim() != ""){
md_add_data("content",mdu,editor.getValue());
var id1 = "#e_tip_"+id;
var id2 = "#e_tips_"+id;
var textStart = " 数据已于 "
var text = textStart + h + ':' + m + ':' + s +" 保存 ";
// 占位符
var oldHtml = $(id2).html();
if (oldHtml && oldHtml != ' ' && oldHtml.startsWith(textStart) == false) {
$(id2).html( oldHtml.split(' (')[0] + ` (${text})`);
} else {
$(id2).html(text);
}
// $(id2).html("");
}
},10000);
}else{
$("#e_tip_"+id).after('您的浏览器不支持localStorage.无法开启自动保存草稿服务,请升级浏览器!');
}
}
function create_editorMD(id, width, high, placeholder, imageUrl, callback, initValue,
onchange, watch, { noStorage, showNullButton }, that) {
// 还是出现了setting只有一份被共用的问题
var editorName = window.editormd(id, {
width: width,
height: high===undefined?400:high,
path: path, // "/editormd/lib/"
markdown : initValue,
dialogLockScreen: false,
watch:watch===undefined?true:watch,
syncScrolling: "single",
tex: true,
tocm: true,
emoji: true,
taskList: true,
codeFold: true,
searchReplace: true,
htmlDecode: "style,script,iframe",
sequenceDiagram: true,
autoFocus: false,
// mine
toolbarIcons: function (mdEditor) {
//
// let react_id = `react_${id}`;
// const __that = window[react_id]
// Or return editormd.toolbarModes[name]; // full, simple, mini
// Using "||" set icons align right.
const icons = ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "|", "testIcon", "testIcon1", '|', "image", "table", '|', "watch", "clear"];
// if (__that.props.showNullButton) {
// icons.push('nullBtton')
// }
return icons
},
toolbarCustomIcons: {
testIcon: "<a type=\"inline\" class=\"latex\" ><div class='zbg'></div></a>",
testIcon1: "<a type=\"latex\" class=\"latex\" ><div class='zbg_latex'></div></a>",
nullBtton: "<a type=\"nullBtton\" class='pr' title='增加填空'><div class='border-left'><span></span></div><span class='fillTip'>点击插入填空项</span><i class=\"iconfont icon-edit font-16\"></i></a>",
},
//这个配置在simple.html中并没有但是为了能够提交表单使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中方便post提交表单。
saveHTMLToTextarea: true,
// 用于增加自定义工具栏的功能可以直接插入HTML标签不使用默认的元素创建图标
dialogMaskOpacity: 0.6,
placeholder: placeholder,
imageUpload: true,
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL: imageUrl,//url
onchange: onchange,
onload: function() {
let _id = this.id // 如果要使用this这里不能使用箭头函数
let _editorName = this;
let react_id = `react_${_editorName.id}`;
const __that = window[react_id]
// this.previewing();
// let _id = id;
$("#" + _id + " [type=\"latex\"]").bind("click", function () {
_editorName.cm.replaceSelection("```latex");
_editorName.cm.replaceSelection("\n");
_editorName.cm.replaceSelection("\n");
_editorName.cm.replaceSelection("```");
var __Cursor = _editorName.cm.getDoc().getCursor();
_editorName.cm.setCursor(__Cursor.line - 1, 0);
});
$("#" + _id + " [type=\"inline\"]").bind("click", function () {
_editorName.cm.replaceSelection("$$$$");
var __Cursor = _editorName.cm.getDoc().getCursor();
_editorName.cm.setCursor(__Cursor.line, __Cursor.ch - 2);
_editorName.cm.focus();
});
$("[type=\"inline\"]").attr("title", "行内公式");
$("[type=\"latex\"]").attr("title", "多行公式");
if (__that.props.showNullButton) {
const NULL_CH = '▁'
// const NULL_CH = ''
// const NULL_CH = '🈳'
$("#" + _id + " [type=\"nullBtton\"]").bind("click", function () {
_editorName.cm.replaceSelection(NULL_CH);
// var __Cursor = _editorName.cm.getDoc().getCursor();
// _editorName.cm.setCursor(__Cursor.line - 1, 0);
});
}
if (noStorage == true) {
} else {
md_elocalStorage(_editorName, `MDEditor__${_id}`, _id);
}
callback && callback(_editorName)
}
});
return editorName;
}
export default class MDEditors extends Component {
constructor(props) {
super(props)
this.state = {
initValue: ''
}
}
componentDidUpdate(prevProps, prevState) {
// 不能加,影响了试卷填空题
// if (this.props.initValue != prevProps.initValue) {
// this.answers_editormd.setValue(this.props.initValue)
// }
}
// react_mdEditor_
componentDidMount = () => {
const { mdID, initValue, placeholder, showNullButton} = this.props;
let _id = `mdEditor_${mdID}`
this.contentChanged = false;
const _placeholder = placeholder || "";
// amp;
// 编辑时要传memoId
const imageUrl = `/upload_with_markdown?container_id=&container_type=Memo`;
// 创建editorMd
let react_id = `react_${_id}`;
window[react_id] = this
const answers_editormd = create_editorMD(_id, '100%', this.props.height, _placeholder, imageUrl, (__editorName) => {
react_id = `react_${__editorName.id}`;
const that = window[react_id]
setTimeout(() => {
console.log('timeout', __editorName.id)
__editorName.resize()
__editorName.cm && __editorName.cm.refresh()
}, that.props.refreshTimeout || 500)
if (that.props.initValue != undefined && that.props.initValue != '') {
__editorName.setValue(that.props.initValue)
}
if (that.state.initValue) {
__editorName.setValue(that.state.initValue)
}
__editorName.cm.on("change", (_cm, changeObj) => {
that.contentChanged = true;
if (that.state.showError) {
that.setState({showError: false})
}
that.onEditorChange()
})
that.props.onCMBlur && __editorName.cm.on('blur', () => {
that.props.onCMBlur()
})
that.props.onCMBeforeChange && __editorName.cm.on('beforeChange', (cm,change) => {
that.props.onCMBeforeChange(cm,change)
})
that.answers_editormd = __editorName;
window[_id] = __editorName;
}, initValue, this.onEditorChange,this.props.watch, {
noStorage: this.props.noStorage,
showNullButton: this.props.showNullButton
}, this);
}
showError = () => {
this.setState({showError: true})
}
onEditorChange = () => {
this.props.setcheckoutcontent()
if (!this.answers_editormd) return;
const val = this.answers_editormd.getValue();
try {
this.props.onChange && this.props.onChange(val)
} catch(e) {
// http://localhost:3007/courses/1309/common_homeworks/6566/setting
// 从这个页面,跳转到编辑页面,再在编辑页面点击返回的时候,这里会报错
console.error('出错')
console.error(e)
}
}
resize = () => {
if (!this.answers_editormd) { // 还未初始化
return;
}
this.answers_editormd.resize()
this.answers_editormd.cm && this.answers_editormd.cm.refresh()
this.answers_editormd.cm.focus()
}
getValue = () => {
try {
return this.answers_editormd.getValue()
} catch (e) {
return ''
}
}
setValue = (val) => {
try {
this.answers_editormd.setValue(val)
} catch (e) {
// TODO 这里多实例的时候前一个实例的state会被后面这个覆盖 参考NewWork.js http://localhost:3007/courses/1309/homework/9300/edit/1
// 未初始化
this.setState({ initValue: val })
}
}
render() {
let {
showError
} = this.state;
let { mdID, className, noStorage } = this.props;
let _style = {}
if (showError) {
_style.border = '1px solid red'
}
return (
<React.Fragment>
<div className={`df ${className} mt20`} >
{/* padding10-20 */}
<div className="edu-back-greyf5 radius4" id={`mdEditor_${mdID}`} style={{..._style}}>
<textarea style={{display: 'none'}} id="evaluate_script_show" name="content" ></textarea>
<div className="CodeMirror cm-s-defualt">
</div>
</div>
</div>
<div className={"fr rememberTip"}>
{noStorage == true ? ' ' : <p id={`e_tips_mdEditor_${mdID}`} className="edu-txt-right color-grey-cd font-12"> </p>}
{/* {noStorage == true ? ' ' : <p id={`e_tips_mdEditor_${mdID}`} className="edu-txt-right color-grey-cd font-12"> </p>} */}
</div>
</React.Fragment>
)
}
}

@ -0,0 +1,25 @@
import React, {Component} from 'react';
import {Link} from "react-router-dom";
class PackageBanner extends Component {
constructor(props) {
super(props)
this.state = {
}
}
componentDidMount() {
}
render() {
return (
<div className="project_packagesHead" style={{height: '300px'}}></div>
)
}
}
export default PackageBanner;

@ -0,0 +1,299 @@
import React, {Component} from 'react';
import {Link} from "react-router-dom";
import axios from 'axios';
import { Input ,Icon,Button,Pagination} from 'antd';
import moment from 'moment';
import '../packageconcnet.css';
const { Search } = Input;
let categorylist=[
{name:"全部",value:undefined},
{name:"前端开发",value:"front"},
{name:"后端开发",value:"backend"},
{name:"移动开发",value:"mobile"},
{name:"数据库",value:"database"},
{name:"云计算和大数据",value:"cloud_compute_and_big_data"},
{name:"人工智能",value:"ai"},
{name:"运维与测试",value:"devops_and_test"},
{name:"其他",value:"other"},
]
//
function setcategorylist(val){
let vals=""
categorylist.some((item,key)=> {
if (item.name === val) {
vals=item.value
return true
}
}
)
return vals
}
class PackageConcent extends Component {
constructor(props) {
super(props)
this.state = {
data:undefined,
project_packages:undefined,
category:undefined,
keyword:undefined,
sort_by:"recently",
sort_direction:"desc",
page:1,
per_page:20,
categories:[]
}
}
//desc, desc, asc
//否 string 排序,默认最新, recently, price
// 否 string 类型, front,backend,mobile,database, cloud_compute_and_big_data,devops_and_test,ai,other
componentDidMount() {
window.document.title = '众包创新'
let {category,keyword,sort_by,sort_direction,page}=this.state
this.setdatas(category,keyword,sort_by,sort_direction,page)
let Url = `/project_package_categories.json`;
axios.get(Url).then((response) => {
// console.log(response)
this.setState({
categories:response.data.categories
})
}).catch((error) => {
console.log(error)
})
}
setdatas=(category,keyword,sort_by,sort_direction,page)=>{
let Url = `/project_packages.json`;
axios.get(Url,{params:{
category_id:category,
keyword:keyword,
sort_by:sort_by,
sort_direction:sort_direction,
page:page,
per_page:20,
}}
).then((response) => {
this.setState({
data:response.data,
project_packages:response.data.project_packages
})
}).catch((error) => {
console.log(error)
})
}
setdatafuns=(value)=>{
let {category,keyword,sort_by,sort_direction,page}=this.state
this.setState({
keyword:value
})
this.setdatas(category,value,sort_by,sort_direction,page)
}
setcategory=(value)=>{
let {category,keyword,sort_by,sort_direction,page}=this.state
this.setState({
category:value
})
this.setdatas(value,keyword,sort_by,sort_direction,page)
}
setsort_byfun=(value)=>{
let {category,keyword,sort_by,sort_direction,page}=this.state
this.setState({
sort_by:value
})
let sort_directionvalue;
if(value===sort_by){
if(sort_direction==="desc"){
this.setState({
sort_direction:"asc"
})
sort_directionvalue="asc";
}else{
this.setState({
sort_direction:"desc"
})
sort_directionvalue="desc";
}
}else{
this.setState({
sort_direction:"desc"
})
sort_directionvalue="desc";
}
this.setdatas(category,keyword,value,sort_directionvalue,page)
}
render() {
let {data,page,category,sort_by,sort_direction,project_packages}=this.state;
return (
<div className="educontent clearfix mtf10" style={{flex: "1 0 auto"}}>
<div className="stud-class-set">
<div className="news">
<div className="edu-class-inner container clearfix">
<div className="member for-content-0 for-content">
<div className="people clearfix mb25">
{/*concent*/}
<div className="mb30">
<div className="clearfix">
<p className="clearfix" >
<p style={{height: '50px'}}>
<Search placeholder="输入标题名称进行检索"
style={{ width: 749}}
className="packinput"
enterButton={<span><Icon type="search" className="mr5"/> 搜索</span>}
onSearch={ (value)=>this.setdatafuns(value)} />
<Button type="primary" className="setissues fr" size={"large"}>
<a href="/crowdsourcings/new" >发布需求</a>
</Button>
</p>
</p>
</div>
</div>
<div className="edu-back-white mb30">
<p className="clearfix padding30">
<p className="clearfix mb30 shaiContent">
<span className="shaiTitle fl mt3">类型</span>
<div className="fl pr shaiAllItem pagetype">
<li className={category===undefined?"shaiItem shixun_repertoire active":"shaiItem shixun_repertoire"} onClick={()=>this.setcategory(undefined)}>全部</li>
{this.state.categories.map((item,key)=>{
return(
<li key={key} className={category===item.id?"shaiItem shixun_repertoire active":"shaiItem shixun_repertoire"} value={item.id} onClick={()=>this.setcategory(item.id)}>{item.name}</li>
)
})}
</div>
</p>
<p className="clearfix shaiContent">
<span className="shaiTitle fl mt3">排序</span>
<div className="fl pr shaiAllItem">
<li className="shaiItem shixun_repertoire" value="recently" onClick={()=>this.setsort_byfun("recently")}>
<span className={sort_by==="recently"?"color-blue":""}>最新</span>
<sapn className="relativef">
<i className={sort_by==="recently"&&sort_direction==="asc"?
"iconfont icon-sanjiaoxing-up font-12 topsj color-blue" :"iconfont icon-sanjiaoxing-up font-12 topsj"}></i>
<i className={sort_by==="recently"&&sort_direction==="desc"?
"iconfont icon-sanjiaoxing-down font-12 bottomsj color-blue":"iconfont icon-sanjiaoxing-down font-12 bottomsj"}></i>
</sapn>
</li>
<li className="shaiItem shixun_repertoire " value="price" onClick={()=>this.setsort_byfun("price")}>
<span className={sort_by==="price"?"color-blue":""}>价格</span>
<sapn className="relativef">
<i className={sort_by==="price"&&sort_direction==="asc"?
"iconfont icon-sanjiaoxing-up font-12 topsj color-blue" :"iconfont icon-sanjiaoxing-up font-12 topsj"}></i>
<i className={sort_by==="price"&&sort_direction==="desc"?
"iconfont icon-sanjiaoxing-down font-12 bottomsj color-blue":"iconfont icon-sanjiaoxing-down font-12 bottomsj"}></i>
</sapn>
</li>
</div>
</p>
</p>
</div>
{project_packages&&project_packages.map((item,key)=>{
return(
<div className="educontent project-packages-list mb30" key={key}>
<div className="project-package-item">
<div className="item-image">
<img src={"/images/educoder/project_packages/"+setcategorylist(item.category_name)+".png"}/>
</div>
<div className=" item-body">
<div className=" item-head mbf10">
<div className=" item-head-title">
<a className={"fl mt3 font-20 font-bd color-dark maxwidth700 "}
href={"/crowdsourcings/"+item.id}
title={item.title}
>{item.title}</a>
</div>
<div className=" item-head-blank"></div>
<div className=" item-head-price">
{item.min_price===null?"":<span>{item.min_price}</span>}
{item.max_price===null||item.min_price===null?"":<span>~</span>}
{item.max_price===null?"":<span>{item.max_price}</span>}
{item.min_price===null&&item.max_price===null?<span>可议价</span>:""}
</div>
</div>
<div className=" item-category mt10">
<div className=" item-category-item">{item.category_name}</div>
</div>
<div className=" item-other">
<div className=" item-group item-other-visit">
<span className=" item-group-icon mr10"><i className="fa fa-eye"></i></span>
<span className=" item-group-text">{item.visit_count}人浏览</span>
</div>
<div className=" item-group item-other-deadline">
<span className=" item-group-icon mr10"><i className="fa fa-clock-o"></i></span>
<span className=" item-group-text">{moment(item.deadline_at).endOf('day').fromNow()}竞标截止</span>
</div>
<div className=" item-group item-other-bidding ml0">
<span className=" item-group-icon mr10"><i className="fa fa-user" ></i></span>
<span className=" item-group-text">{item.bidding_users_count}人竞标</span>
</div>
<div className=" item-other-blank"></div>
<div className=" item-group item-other-publish-at">
{item.published_at===null?<span className="item-group-text">更新于{moment(item.updated_at).format("YYYY-MM-DD HH:mm")} </span>:
<span className=" item-group-text">发布于{moment(item.published_at).format("YYYY-MM-DD HH:mm")} </span>}
</div>
</div>
</div>
</div>
</div>
)
})}
{project_packages&&project_packages.length===0?<div className="edu-back-white">
<div className="edu-tab-con-box clearfix edu-txt-center">
<img className="edu-nodata-img mb20" src="https://www.educoder.net/images/educoder/nodata.png" />
<p className="edu-nodata-p mb20">暂无数据哦~</p></div>
</div>:""}
<div className={"mt40"}>
<Pagination className="edu-txt-center" hideOnSinglePage={true} pageSize={20} current={page} total={data&&data.count} />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
export default PackageConcent;

@ -0,0 +1,30 @@
import React, { Component } from 'react';
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
//业务组件
import PackageBanner from "./PackageBanner";
import PackageConcent from "./PackageConcent";
class PackageIndex extends Component{
constructor(props) {
super(props)
}
componentDidMount(){
window.document.title = '众包创新'
}
render() {
return (
<div>
<div className="clearfix">
{/*头部banner*/}
<PackageBanner {...this.props}></PackageBanner>
{/*内容banner*/}
<PackageConcent {...this.props}></PackageConcent>
</div>
</div>
)
}
}
export default PackageIndex;

@ -0,0 +1,41 @@
import React, { Component } from 'react';
import { Spin, Icon , Modal,Input,Button} from 'antd';
class NEITaskDetailsModel extends Component {
constructor(props) {
super(props);
this.state = {
}
}
render() {
return(
<Modal
keyboard={false}
title="提示"
visible={this.props.applytype===undefined?false:this.props.applytype}
closable={false}
footer={null}
destroyOnClose={true}
centered={true}
width="530px"
>
<div className="task-popup-content">
<p className="task-popup-text-center font-16 mb20">
<div>{this.props.applyvalue}</div>
<div>{this.props.applybottom}</div>
</p>
<div className="clearfix mt30 edu-txt-center">
<a className="task-btn mr30 color_white" onClick={this.props.applycancel}>取消</a>
<a className="task-btn task-btn-orange " onClick={this.props.applyconfirm}>确定</a>
</div>
</div>
</Modal>
)
}
}
export default NEITaskDetailsModel;

@ -0,0 +1,487 @@
import React, {Component} from 'react';
import {Link} from "react-router-dom";
import axios from 'axios';
import { Input ,Icon,Button,Pagination,DatePicker,Breadcrumb} from 'antd';
import { handleDateString,markdownToHTML,bytesToSize,getImageUrl} from 'educoder';
import NEITaskDetailsModel from './NEITaskDetailsModel';
import moment from 'moment';
import '../packageconcnet.css';
import './pds.css'
import gouxuan from './img/gouxuan.png'
import weigouxuan from './img/weigouxuan.png'
const { Search } = Input;
// let categorylist=[
// {name:"全部",value:undefined},
// {name:"前端开发",value:"front"},
// {name:"后端开发",value:"backend"},
// {name:"移动开发",value:"mobile"},
// {name:"数据库",value:"database"},
// {name:"云计算和大数据",value:"cloud_compute_and_big_data"},
// {name:"人工智能",value:"ai"},
// {name:"其他",value:"other"},
// ]
//
// function setcategorylist(val){
// let vals=""
// categorylist.some((item,key)=> {
// if (item.value === val) {
// vals=item.name
// return true
// }
// }
// )
//
// return vals
// }
class PackageIndexNEITaskDetails extends Component {
constructor(props) {
super(props)
this.contentMdRef = React.createRef();
this.state = {
data:undefined,
modalCancel: false,
overtype:false,
setbiddingmantype:false,
datalist:[]
}
}
componentDidMount() {
this.getdatas()
window.document.title = '众包创新'
}
getdatas=()=>{
let url =`/project_packages/${this.props.match.params.id}.json`;
axios.get(url).then((response) => {
this.setState({
data:response.data
})
}).catch((error) => {
console.log(error);
})
}
setbiddingman=()=>{
this.setState({
setbiddingmantype:true
})
}
notsetbiddingman=()=>{
let {data} =this.state;
let gouxuans2=data.bidding_users
for (var i=0;i<gouxuans2.length;i++){
if(gouxuans2[i].bool === true){
gouxuans2[i].bool=false;
}
}
this.setState({
setbiddingmantype:false,
datalist:[]
})
}
modalCancel=()=>{
this.setState({
modalCancel:false
})
}
setbiddingusers=()=>{
let{datalist}=this.state;
if(datalist.length>0){
this.setState({
applytype:true,
applyvalue:`选择的${datalist.length}个竞标者将被设定为“中标”`,
applybottom:"是否确认执行?",
applycancel:this.setApplycancel,
applyconfirm:this.setApplysumbit
})
}
}
setApplysumbit=()=>{
this.setState({
applytype:false,
})
let{datalist}=this.state;
let newlist=[];
datalist.map((item,key)=>{
newlist.push(item.id)
})
let url=`/project_packages/${this.props.match.params.id}/bidding_users/win.json`;
axios.post(url,{
user_ids:newlist
}).then((response) => {
if(response.data.status===0){
this.props.showSnackbar("提交成功");
this.setState({
setbiddingmantype:false
})
this.getdatas()
}else if(response.data.status===-1){
this.props.showSnackbar(response.data.message);
}
}).catch((error) => {
console.log(error)
})
}
Clickteacher2=(e)=>{
let {data} =this.state;
let newlist=[]
let gouxuans2=data.bidding_users
for (var i=0;i<gouxuans2.length;i++){
if(gouxuans2[i].id === e){
// console.log("51");
// console.log(e);
if(gouxuans2[i].bool === true){
gouxuans2[i].bool=false;
}else{
gouxuans2[i].bool=true;
newlist.push(gouxuans2[i])
}
}else{
if(gouxuans2[i].bool === true){
newlist.push(gouxuans2[i])
}
}
}
console.log(newlist);
this.setState({
datalist:newlist,
})
}
setover=()=>{
this.setState({
overtype:true
})
}
setout=()=>{
this.setState({
overtype:false
})
}
deletePackages=()=>{
this.setState({
applytype:true,
applyvalue:"是否确认删除?",
applycancel:this.setApplycancel,
applyconfirm:this.setApplydelect
})
}
setApplydelect=()=>{
this.setState({
applytype:false,
})
let url=`/project_packages/${this.props.match.params.id}.json`;
axios.delete(url ).then((response) => {
// const status = response.data.status
// console.log(response)
this.props.showSnackbar('删除成功');
}).catch((error) => {
console.log(error)
})
}
setBiddingApply=()=>{
this.setState({
applytype:true,
applyvalue:"是否确认报名?",
applycancel:this.setApplycancel,
applyconfirm:this.setApplyconfirm
})
}
setApplycancel=()=>{
this.setState({
applytype:false,
})
}
setApplyconfirm=()=>{
this.setState({
applytype:false,
})
let url=`/project_packages/${this.props.match.params.id}/bidding_users.json`;
axios.post(url).then((response) => {
if(response.data.status===0){
this.props.showSnackbar("报名成功");
this.getdatas()
}else if(response.data.status===-1){
this.props.showSnackbar(response.data.message);
}
}).catch((error) => {
console.log(error)
})
}
goback = () => {
// window.history.go(-1)
window.location.href="/crowdsourcings";
}
render() {
let {overtype,data}=this.state;
// console.log(data&&data.creator.login)
console.log(data)
return (
data===undefined?"":<div>
<div className="clearfix">
<NEITaskDetailsModel
applytype={this.state.applytype}
applyvalue={this.state.applyvalue}
applybottom={this.state.applybottom}
applycancel={this.state.applycancel}
applyconfirm={this.state.applyconfirm}
/>
<div className={"educontent mt20 mb50"}>
<Breadcrumb separator={'>'} className={"fl"}>
{/*<Breadcrumb.Item>{this.props.current_user.username}</Breadcrumb.Item>*/}
<Breadcrumb.Item>
<a href="/crowdsourcings">众包创新</a>
</Breadcrumb.Item>
<Breadcrumb.Item><span className={"tabelcli"} title={data&&data.title}>{data&&data.title}</span></Breadcrumb.Item>
</Breadcrumb>
<a className="color-grey-6 fr font-15 mr20" onClick={this.goback}>返回</a>
<div className="mb20">
<p className="clearfix ">
<div className={"stud-class-set coursenavbox edu-back-white mt20"}>
<div className={"ant-row contentbox mdInForm "}>
<div className="educontent project-packages-list relative">
{data&&data.status==="pending"?<div>
<div className="publicpart orangeBlack "></div>
<span className="smalltrangle"></span>
<span className="publicword publicwords"> 未申请 </span>
</div>:data&&data.status==="applying"?<div>
<div className="publicpart orangeGreen"></div>
<span className="smalltrangle"></span>
<span className="publicword publicwords"> 待发布 </span>
</div>:""}
<div className="project-package-item project-package-items height185">
<div className="item-image">
<div className="fl edu-back-white ">
<a href={`/users/${data&&data.creator.login}`}>
<img alt="头像" className="radius mt10 ml5" height="70" id="nh_user_logo" name="avatar_image"
src={`/images/${data&&data.creator.image_url}`}
width="70"/>
</a>
<div className=" edu-back-white pagemancenter mt10 ">
{data&&data.creator.name}
</div>
{data&&data.creator.login===this.props.current_user&&this.props.current_user.login?"":<div className=" edu-back-white ml5 mt10 "
onMouseOver={this.setover}
onMouseOut={this.setout}
>
{overtype===false?<a className="ContacttheTA fl" target="_blank" href={`/users/${data&&data.creator.login}/message_detail?user_id=${data&&data.creator.id}`}> <img alt="头像" class="mr5" src={require('./newsone.png')} />联系TA</a>:
<a className="ContacttheTAs fl" target="_blank" href={`/users/${data&&data.creator.login}/message_detail?user_id=${data&&data.creator.id}`}> <img alt="头像" className="mr5"
src={require('./newstwo.png')}/>联系TA</a>}
</div>}
</div>
</div>
<div className=" item-body">
<div className=" item-head mbf10">
<div className=" item-head-title ">
<span className={"fl mt3 font-18 font-bd color-dark maxwidth700 "}
title={data&&data.title}
>{data&&data.title}
</span>
</div>
<div className=" item-head-price mtf7">
{data&&data.min_price===null?"":<span><span className={"font-24"}>{data&&data.min_price}</span></span>}
{data&&data.max_price===null||data&&data.min_price===null?"":<span>~</span>}
{data&&data.max_price===null?"":<span><span className={"font-24"}>{data&&data.max_price}</span></span>}
{data&&data.min_price===null&&data&&data.max_price===null?<span>可议价</span>:""}
</div>
</div>
<div className="item-category">
<div className="item-category-item">{data&&data.category_name}</div>
</div>
<div className=" item-other">
<div>
{data&&data.published_at===null?<span className="item-group-text">
更新时间{moment(data&&data.updated_at).format("YYYY-MM-DD HH:mm")}
</span>:<span className="item-group-text">
发布时间{moment(data&&data.published_at).format("YYYY-MM-DD HH:mm")}
</span>}
</div>
<div className=" item-group item-other-deadline ml40">
<span className="item-group-text">竞标截止时间{moment(data&&data.deadline_at).format("YYYY-MM-DD HH:mm")}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/*详情*/}
<div className={"stud-class-set padding26 coursenavbox edu-back-white mt20"}>
<div>
<div className={"longboxs"}>
需求详情
{data&&data.status==="pending"&&data&&data.operation.can_select_bidding_user===true?<div className="fr">
<a className="task-btn-nebules fr" href={`/crowdsourcings/${this.props.match.params.id}/edit`}>编辑</a>
<a className="task-btn-nebules fr" onClick={this.deletePackages}>删除</a>
</div>:""}
</div>
<div className={"padding020"}>
<div className={"markdown-body"} dangerouslySetInnerHTML={{__html: markdownToHTML(data&&data.content).replace(/▁/g,"▁▁▁")}}></div>
</div>
</div>
{data&&data.attachments.length>0?<div>
<div className={"longboxs "}>
需求文件
</div>
{data&&data.attachments.map((item,key)=>{
return(
<div className={"newForm newFormbox mt10 "}>
<i className="color-green iconfont icon-fujian mr5 fl font-14 mt3"></i>
<a className="upload_filename color-grey readonly hidden fl mtf3 mr10 ml5" href={item.url}>{item.title} &nbsp; &nbsp;{bytesToSize(item.filesize)}</a>
</div>
)})}
</div>:""}
</div>
{/*发布者和竞选者状态show*/}
{this.state.setbiddingmantype===false&&data&&data.published_at!=null?<div className={"stud-class-set coursenavbox edu-back-white mt20"}>
{/*下面是头像*/}
<div className={"stud-class-set pd26 coursenavbox edu-back-white"}>
<div className={"relativef"}>
<div className={"longboxs mb0"}>
报名列表({data&&data.bidding_users.length})
</div>
<div className="packageabsolute">
{data&&data.operation.can_bidding===true?<Button type="primary" className="defalutSubmitbtn fl ml20 defalutSubmitbtns" onClick={this.setBiddingApply}>竞标报名</Button>:""}
{data&&data.operation.can_select_bidding_user===true?<Button type="primary" className="defalutSubmitbtn fl ml20 defalutSubmitbtns" onClick={this.setbiddingman}>选择中标者</Button>:""}
</div>
</div>
</div>
<div className={"ysllogin_register_contentss edu-back-white "} style={{borderTop: '1px solid rgb(234, 234, 234)'}}>
<div className="ysllogin_sections">
<div className="ysldivhome2s">
<div style={{height: "20px"}}> </div>
{data&&data.bidding_users.map((item,key)=>{
return(
<div className="ysldivhomediv1s homehove" key={key}>
{item.status==="bidding_won"?<img src={gouxuan} className="yslgouxuanimg"/>:""}
<a href={`/users/${item.login}`}><img className="div1imgs" src={getImageUrl("images/"+item.image_url)}/></a>
<div className="textall mt10" title={item.name}> <p className="ptext">{item.name}</p></div>
{this.props.current_user&&this.props.current_user.login!=item.login?<a className="ContacttheTAs fl none" target="_blank" href={`/users/${item.login}/message_detail?user_id=${item.id}`}>
<img alt="头像" className="mr5" src={require('./newstwo.png')}/>联系TA
</a>:""}
</div>
)
})}
{data&&data.bidding_users.length===0?<div className="edu-back-white" style={{width: '1200px'}}>
<div className="edu-tab-con-box clearfix edu-txt-center">
<img className="edu-nodata-img mb20" src="https://www.educoder.net/images/educoder/nodata.png" />
<p className="edu-nodata-p mb20">暂无人员竞标~</p></div>
</div>:""}
</div>
</div>
</div>
</div>:""}
{this.state.setbiddingmantype===true?<div className={"stud-class-set coursenavbox edu-back-white mt20"}>
{/*发布人选择状态*/}
{/*下面是头像*/}
<div className={"stud-class-set pd26 coursenavbox edu-back-white"}>
<div className={"relativef"}>
<div className={"longboxs mb0"}>
报名列表({data&&data.bidding_users.length})
</div>
<div className="packageabsolute">
<div className=" fl mt10 mr20">
已选 <span className={"color-orange06"}>({this.state.datalist.length})</span>
</div>
<a className="defalutCancelbtns fl" onClick={this.notsetbiddingman}>取消</ a>
<Button type="primary" className="defalutSubmitbtn fl ml20 defalutSubmitbtns" onClick={this.setbiddingusers}>确定</Button>
</div>
</div>
</div>
<div className={"ysllogin_register_contentss edu-back-white "} style={{borderTop: '1px solid rgb(234, 234, 234)'}}>
<div className="ysllogin_sections">
<div className="ysldivhome2s">
<div style={{height: "20px"}}> </div>
{data&&data.bidding_users.map((item,key)=>{
return(
<div className="ysldivhomediv1s" onClick={()=>this.Clickteacher2(item.id)} key={key}>
{item.bool===true?<img src={gouxuan} className="yslgouxuanimg"/>:<img src={weigouxuan} className="yslgouxuanimg"/>}
<a href={`/users/${item.login}`} target="_blank"><img className="div1imgs" src={getImageUrl("images/"+item.image_url)}/></a>
<span className={item.bool===true?"textall mt10 color-blue":"textall mt10"} title={item.name}> <p className="ptext">{item.name}</p></span>
</div>
)
})}
{data&&data.bidding_users.length===0?<div className="edu-back-white" style={{width: '1200px'}}>
<div className="edu-tab-con-box clearfix edu-txt-center">
<img className="edu-nodata-img mb20" src="https://www.educoder.net/images/educoder/nodata.png" />
<p className="edu-nodata-p mb20">暂无人员竞标~</p></div>
</div>:""}
</div>
</div>
</div>
</div>:""}
</p>
</div>
</div>
</div>
</div>
)
}
}
export default PackageIndexNEITaskDetails;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save