Merge branch 'dev_aliyun' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun

dev_forum
jingquan huang 5 years ago
commit f323690885

4
.gitignore vendored

@ -31,6 +31,7 @@
/.idea/* /.idea/*
# Ignore react node_modules # Ignore react node_modules
/public/react/.cache
/public/react/build /public/react/build
/public/react/build/ /public/react/build/
/public/react/node_modules/ /public/react/node_modules/
@ -60,5 +61,4 @@ vendor/bundle/
/public/images/avatars /public/images/avatars
/public/files /public/files
/workspace /workspace
/log /log

@ -91,3 +91,5 @@ gem 'bulk_insert'
# elasticsearch # elasticsearch
gem 'searchkick' gem 'searchkick'
gem 'aasm'

@ -8,6 +8,8 @@ PATH
GEM GEM
remote: https://gems.ruby-china.com/ remote: https://gems.ruby-china.com/
specs: specs:
aasm (5.0.5)
concurrent-ruby (~> 1.0)
actioncable (5.2.1) actioncable (5.2.1)
actionpack (= 5.2.1) actionpack (= 5.2.1)
nio4r (~> 2.0) nio4r (~> 2.0)
@ -324,6 +326,7 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
aasm
active_decorator active_decorator
acts-as-taggable-on (~> 6.0) acts-as-taggable-on (~> 6.0)
awesome_print awesome_print

@ -14,7 +14,7 @@ class AttachmentsController < ApplicationController
update_downloads(@file) update_downloads(@file)
redirect_to @file.cloud_url and return redirect_to @file.cloud_url and return
end end
send_file absolute_path(local_path(@file)), type: @file.content_type.presence || 'application/octet-stream' send_file(absolute_path(local_path(@file)), filename: @file.filename, type: @file.content_type.presence || 'application/octet-stream')
update_downloads(@file) update_downloads(@file)
end end
@ -100,7 +100,7 @@ class AttachmentsController < ApplicationController
def current_month_folder def current_month_folder
date = Time.now date = Time.now
"#{date.year}/#{date.day.to_s.rjust(2, '0')}" "#{date.year}/#{date.month.to_s.rjust(2, '0')}"
end end
def file_ext(file_name) def file_ext(file_name)

@ -0,0 +1,24 @@
class BiddingUsersController < ApplicationController
before_action :require_login, :check_auth
def create
ProjectPackages::BiddingService.call(current_package, current_user)
render_ok
rescue ProjectPackages::BiddingService::Error => ex
render_error(ex.message)
end
def win
package = current_user.project_packages.find(params[:project_package_id])
ProjectPackages::WinBiddingService.call(package, params)
render_ok
rescue ProjectPackages::WinBiddingService::Error => ex
render_error(ex.message)
end
private
def current_package
@_current_package ||= ProjectPackage.find(params[:project_package_id])
end
end

@ -11,7 +11,10 @@ class CoursesController < ApplicationController
render_error(ex.model.errors.full_messages.join(',')) render_error(ex.model.errors.full_messages.join(','))
end end
before_action :require_login, :check_auth, except: [:index, :show, :students, :teachers, :board_list, :mine, :all_course_groups, :left_banner, :top_banner] before_action :require_login, except: [:index, :show, :students, :teachers, :board_list, :mine, :all_course_groups,
:left_banner, :top_banner]
before_action :check_auth, except: [:index, :show, :students, :teachers, :board_list, :mine, :all_course_groups,
:left_banner, :top_banner, :apply_to_join_course]
before_action :set_course, :user_course_identity, only: [:show, :update, :destroy, :settings, :set_invite_code_halt, before_action :set_course, :user_course_identity, only: [:show, :update, :destroy, :settings, :set_invite_code_halt,
:set_public_or_private, :search_teacher_candidate, :teachers, :apply_teachers, :set_public_or_private, :search_teacher_candidate, :teachers, :apply_teachers,
:top_banner, :left_banner, :add_teacher_popup, :add_teacher, :top_banner, :left_banner, :add_teacher_popup, :add_teacher,
@ -881,11 +884,11 @@ class CoursesController < ApplicationController
end end
# 验证是否存在同学号的学生 # 验证是否存在同学号的学生
u_extension = current_user.user_extension # u_extension = current_user.user_extension
if params[:student].present? && u_extension.student? # if params[:student].present? && u_extension.student?
same_student_id_users = UserExtension.where.not(user_id: current_user.id).where(student_id: u_extension.student_id, identity: %i[student], school_id: u_extension.school_id).pluck(:user_id) # same_student_id_users = UserExtension.where.not(user_id: current_user.id).where(student_id: u_extension.student_id, identity: %i[student], school_id: u_extension.school_id).pluck(:user_id)
tip_exception("该课堂已存在同学号的学生,暂时无法加入,请联系老师") if course.students.exists?(user_id: same_student_id_users) # tip_exception("该课堂已存在同学号的学生,暂时无法加入,请联系老师") if course.students.exists?(user_id: same_student_id_users)
end # end
# 创建学生身份 # 创建学生身份
if params[:student].present? if params[:student].present?
@ -903,6 +906,7 @@ class CoursesController < ApplicationController
CourseAddStudentCreateWorksJob.perform_later(course.id, [current_user.id]) CourseAddStudentCreateWorksJob.perform_later(course.id, [current_user.id])
StudentJoinCourseNotifyJob.perform_later(current_user.id, course.id) StudentJoinCourseNotifyJob.perform_later(current_user.id, course.id)
logger.info("#####################{course.id}")
end end
end end

@ -0,0 +1,6 @@
class ProjectPackageCategoriesController < ApplicationController
def index
categories = ProjectPackageCategory.cached_data
render_ok(count: categories.size, categories: categories)
end
end

@ -0,0 +1,78 @@
class ProjectPackagesController < ApplicationController
include PaginateHelper
before_action :require_login, :check_auth, only: %i[create update destroy]
helper_method :current_package, :package_manageable?
def index
packages = ProjectPackage.where(status: %w(published bidding_ended bidding_finished))
packages = packages.where(project_package_category_id: params[:category_id]) if params[:category_id].present?
keyword = params[:keyword].to_s.strip
packages = packages.where('title LIKE ?', "%#{keyword}%") if keyword.present?
@count = packages.count
direction = params[:sort_direction] == 'asc' ? 'asc' : 'desc'
sort = params[:sort_by] == 'price' ? 'min_price' : 'published_at'
packages = packages.order("#{sort} #{direction}")
@packages = paginate packages.includes(:creator, :attachments, :project_package_category, bidding_users: :user)
end
def show
return render_forbidden unless current_package.visitable? || package_manageable?
current_package.increment!(:visit_count)
end
def create
package = current_user.project_packages.new
ProjectPackages::SaveService.call(package, save_params)
package.increment!(:visit_count)
render_ok(id: package.id)
rescue ProjectPackages::SaveService::Error => ex
render_error(ex.message)
end
def update
package = current_user.project_packages.find(params[:id])
return render_error('该状态下不能编辑') unless package.editable?
ProjectPackages::SaveService.call(package, save_params)
package.increment!(:visit_count)
render_ok(id: package.id)
rescue ProjectPackages::SaveService::Error => ex
render_error(ex.message)
end
def destroy
package = ProjectPackage.find(params[:id])
return render_forbidden unless package.deletable? && package_manageable?
package.destroy!
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1, container_id: package.id,
container_type: 'ProjectPackage', tiding_type: 'Destroyed', extra: package.title)
render_ok
end
private
def current_package
@_current_package ||= ProjectPackage.find(params[:id])
end
def package_manageable?
current_user&.id == current_package.creator_id || admin_or_business?
end
def save_params
params.permit(*%i[category_id title content attachment_ids deadline_at min_price max_price
contact_name contact_phone code publish])
end
end

@ -206,7 +206,7 @@ class StudentWorksController < ApplicationController
@current_user = current_user @current_user = current_user
@work_members = @homework.homework_type != "group" ? [] : @homework.student_works.where.not(user_id: @work.user_id). @work_members = @homework.homework_type != "group" ? [] : @homework.student_works.where.not(user_id: @work.user_id).
where(group_id: @work.group_id).includes(:user) where(group_id: @work.group_id).includes(:user)
@attachments = @work.attachments.where.not(attachtype: 7) @attachments = @work.attachments.where("attachtype != 7 or attachtype is null")
end end
# 判断项目是否已有其他作品关联上了 # 判断项目是否已有其他作品关联上了

@ -7,6 +7,7 @@ class SubjectsController < ApplicationController
:up_member_position, :down_member_position] :up_member_position, :down_member_position]
include ApplicationHelper include ApplicationHelper
include SubjectsHelper
def index def index
@tech_system = Repertoire.where(nil).order("updated_at desc") @tech_system = Repertoire.where(nil).order("updated_at desc")
@ -81,11 +82,13 @@ class SubjectsController < ApplicationController
@is_creator = current_user.creator_of_subject?(@subject) @is_creator = current_user.creator_of_subject?(@subject)
# 合作团队 # 合作团队
@members = @subject.subject_members.includes(:user) @members = @subject.subject_members.includes(:user)
challenge_ids = Challenge.where(shixun_id: @subject.shixuns.published.pluck(:id)).pluck(:id) @shixuns = @subject.shixuns.published.pluck(:id)
challenge_ids = Challenge.where(shixun_id: @shixuns).pluck(:id)
# 实训路径中的所有实训标签 # 实训路径中的所有实训标签
@tags = ChallengeTag.where(challenge_id: challenge_ids).pluck(:name).uniq @tags = ChallengeTag.where(challenge_id: challenge_ids).pluck(:name).uniq
# 用户获取的实训标签 # 用户获取的实训标签
@user_tags = @subject.shixuns.map(&:user_tags_name).flatten.uniq # @user_tags = @subject.shixuns.map(&:user_tags_name).flatten.uniq
@user_tags = user_shixun_tags challenge_ids, @user.id
# 访问数变更 # 访问数变更
@subject.increment!(:visits) @subject.increment!(:visits)
@ -191,7 +194,7 @@ class SubjectsController < ApplicationController
stages.each do |stage| stages.each do |stage|
category = CourseSecondCategory.where(name: stage.name, course_id: @course.id, category_type: "shixun_homework").first || category = CourseSecondCategory.where(name: stage.name, course_id: @course.id, category_type: "shixun_homework").first ||
CourseSecondCategory.create!(name: stage.name, course_id: @course.id, category_type: "shixun_homework", CourseSecondCategory.create!(name: stage.name, course_id: @course.id, category_type: "shixun_homework",
course_module_id: course_module, position: course_module.course_second_categories.count + 1) course_module_id: course_module.id, position: course_module.course_second_categories.count + 1)
stage.shixuns.where(id: params[:shixun_ids], status: 2).each do |shixun| stage.shixuns.where(id: params[:shixun_ids], status: 2).each do |shixun|
homework = HomeworksService.new.create_homework shixun, @course, category, current_user homework = HomeworksService.new.create_homework shixun, @course, category, current_user

@ -3,6 +3,12 @@ class UsersController < ApplicationController
before_action :load_user, only: [:show, :homepage_info] before_action :load_user, only: [:show, :homepage_info]
before_action :check_user_exist, only: [:show, :homepage_info] before_action :check_user_exist, only: [:show, :homepage_info]
# 检查是否更新
def system_update
@notice = SystemUpdateNotice.last
end
def show;end def show;end
def update def update

@ -0,0 +1,5 @@
module ProjectPackageDecorator
extend ApplicationDecorator
display_time_method :updated_at, :deadline_at, :published_at
end

@ -0,0 +1,15 @@
class ProjectPackages::SaveForm
include ActiveModel::Model
attr_accessor :category_id, :title, :content, :attachment_ids, :deadline_at,
:min_price, :max_price, :contact_name, :contact_phone, :code, :publish
validates :category_id, presence: true
validates :title, presence: true, length: { maximum: 60 }
validates :content, presence: true
validates :deadline_at, presence: true
validates :min_price, numericality: { greater_than: 0 }, allow_blank: true
validates :max_price, numericality: { greater_than: ->(obj){ obj.min_price.to_i } }, allow_blank: true
validates :contact_name, presence: true, length: { maximum: 20 }
validates :contact_phone, presence: true, format: { with: /1\d{10}/ }
end

@ -1,13 +1,18 @@
module SubjectsHelper module SubjectsHelper
# 实训路径的发布状态 # 实训路径的发布状态
def publish_status subject, is_manager, user def publish_status subject, is_manager, user, shixuns
status = -1 status = -1
if is_manager if is_manager
status = 0 if subject.status == 0 && subject.shixuns_count > 0 status = 0 if subject.status == 0 && shixuns.count > 0
status = 1 if subject.status == 1 status = 1 if subject.status == 1
status = 2 if subject.status == 2 && user.admin? status = 2 if subject.status == 2 && user.admin?
end end
status status
end end
# 实训路径的所有用户获得的标签
def user_shixun_tags challenge_ids, user_id
ChallengeTag.joins(challenge: [:games]).where(games: {status: 2, user_id: user_id}, challenges: {id:challenge_ids}).pluck("challenge_tags.name").uniq
end
end end

@ -0,0 +1,24 @@
class BiddingUser < ApplicationRecord
include AASM
belongs_to :user
belongs_to :project_package, counter_cache: true
aasm(:status) do
state :pending, initiali: true
state :bidding_won
state :bidding_lost
event :win do
transitions from: [:pending], to: :bid_won
end
event :lose do
transitions from: [:pending], to: :bid_lost
end
end
def status_text
I18n.t("bidding_user.status.#{status}")
end
end

@ -1,5 +1,5 @@
class ChallengeChoose < ApplicationRecord class ChallengeChoose < ApplicationRecord
default_scope {order("position asc")} default_scope {order("challenge_chooses.position asc")}
belongs_to :challenge, optional: true belongs_to :challenge, optional: true
has_many :challenge_tags, :dependent => :destroy has_many :challenge_tags, :dependent => :destroy
has_many :challenge_questions, dependent: :destroy has_many :challenge_questions, dependent: :destroy

@ -0,0 +1,78 @@
class ProjectPackage < ApplicationRecord
include AASM
belongs_to :creator, class_name: 'User'
belongs_to :project_package_category
has_many :project_package_applies, dependent: :destroy
has_one :process_project_package_apply, -> { where(status: :pending) }, class_name: 'ProjectPackageApply'
has_many :bidding_users, dependent: :delete_all
has_many :win_bidding_users, -> { where(status: :bidding_won) }, class_name: 'BiddingUser'
has_many :lose_bidding_users, -> { where(status: :bidding_lost) }, class_name: 'BiddingUser'
has_many :attachments, as: :container, dependent: :destroy
aasm(:status) do
state :pending, initiali: true
state :applying
state :refused
state :published
state :bidding_ended
state :bidding_finished
event :apply do
transitions from: [:pending, :refused], to: :applying
end
event :refuse do
transitions from: :applying, to: :refused
end
event :publish do
transitions from: :applying, to: :published
end
event :end_bidding do
transitions from: :published, to: :bidding_ended
end
event :finish_bidding do
transitions from: [:bidding_ended], to: :bidding_finished
end
end
def category_name
project_package_category.name
end
def visitable?
!editable?
end
def editable?
pending? || applying? || refused?
end
def deletable?
pending? || refused?
end
def deadline?
deadline_at < Time.now
end
def bidding_end?
flag = deadline?
ProjectPackages::EndBiddingService.call(self) if flag && may_end_bidding?
flag
end
def can_bidding?(user)
published? && !bidding_end? && user.id != creator_id && !bidding_users.exists?(user_id: user.id)
end
def status_text
I18n.t("project_package.status.#{status}")
end
end

@ -0,0 +1,19 @@
class ProjectPackageApply < ApplicationRecord
include AASM
belongs_to :project_package
aasm(:status) do
state :pending, initiali: true
state :refused
state :agreed
event :refuse do
transitions from: :pending, to: :refused
end
event :agree do
transitions from: :pending, to: :agreed
end
end
end

@ -0,0 +1,23 @@
class ProjectPackageCategory < ApplicationRecord
default_scope { order(position: :asc) }
has_many :project_packages, dependent: :destroy
after_commit :reset_cache_data
def self.cached_data
Rails.cache.fetch(data_cache_key, expires_in: 1.days) do
ProjectPackageCategory.select(:id, :name).as_json
end
end
def self.data_cache_key
'project_package_category/cached_data'
end
private
def reset_cache_data
Rails.cache.delete(self.class.data_cache_key)
end
end

@ -56,6 +56,7 @@ class Shixun < ApplicationRecord
scope :visible, -> { where.not(status: -1) } scope :visible, -> { where.not(status: -1) }
scope :published, lambda{ where(status: 2) } scope :published, lambda{ where(status: 2) }
scope :published_closed, lambda{ where(status: [2, 3]) }
scope :unhidden, lambda{ where(hidden: 0, status: 2) } scope :unhidden, lambda{ where(hidden: 0, status: 2) }
scope :field_for_recommend, lambda{ select([:id, :name, :identifier, :myshixuns_count]) } scope :field_for_recommend, lambda{ select([:id, :name, :identifier, :myshixuns_count]) }
scope :find_by_ids,lambda{|k| where(id:k)} scope :find_by_ids,lambda{|k| where(id:k)}

@ -53,11 +53,9 @@ class Subject < ApplicationRecord
end end
def my_subject_score def my_subject_score
shixuns_id = self.stage_shixuns.map(&:shixun_id) shixuns_id = shixuns.published_closed.pluck(:id)
shixuns_id = Shixun.where(:id => shixuns_id, :status =>[2, 3]).map(&:id) my_shixun_score = Challenge.joins(:games).where(games: {status: 2, user_id: User.current.id}, shixun_id: shixuns_id).pluck(:score).sum
shixuns_id = shixuns_id.present? ? shixuns_id.join(",") : -1 my_choose_score = ChallengeChoose.joins(challenge: :games).where(games: {status: 2, user_id: User.current.id}, challenges: {shixun_id: shixuns_id}).pluck(:score).sum
my_shixun_score = Challenge.find_by_sql("select sum(c.score) as score FROM challenges c join games g on g.challenge_id = c.id where g.status = 2 and g.user_id = #{User.current.id} and c.shixun_id in(#{shixuns_id})").first.try(:score)
my_choose_score = ChallengeChoose.find_by_sql("SELECT sum(g.final_score) score FROM (`challenge_chooses` cc join challenges c on cc.challenge_id = c.id) join games g on g.challenge_id = c.id where g.status = 2 and g.user_id = #{User.current.id} and c.shixun_id in(#{shixuns_id})").first.try(:score)
return my_shixun_score.to_i + my_choose_score.to_i return my_shixun_score.to_i + my_choose_score.to_i
end end
@ -70,9 +68,8 @@ class Subject < ApplicationRecord
end end
def my_subject_progress def my_subject_progress
shixun_id = self.stage_shixuns.map(&:shixun_id) my_challenge_count = Game.joins(:challenge).where(user_id: User.current.id, status: 2, challenges: {shixun_id: shixuns.published_closed}).
challenge_id = Game.where(:user_id => User.current.id, :status => 2).pluck(:challenge_id) pluck(:challenge_id).uniq.size
my_challenge_count = Challenge.where(:id => challenge_id, :shixun_id => shixun_id).count
count = self.subject_challenge_count == 0 ? 0 : ((my_challenge_count.to_f / self.subject_challenge_count).round(2) * 100).to_i count = self.subject_challenge_count == 0 ? 0 : ((my_challenge_count.to_f / self.subject_challenge_count).round(2) * 100).to_i
end end

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

@ -0,0 +1,36 @@
class ProjectPackages::AgreeApplyService < ApplicationService
Error = Class.new(StandardError)
attr_reader :apply, :package
def initialize(apply)
@apply = apply
@package = apply.project_package
end
def call
raise Error, '该状态下不能进行此操作' unless apply.may_agree? && package.may_publish?
ActiveRecord::Base.transaction do
apply.agree!
# 发布
package.publish
package.published_at = Time.now
package.save!
# 消息
send_agree_notify!
end
end
private
def send_agree_notify!
Tiding.where(container_id: package.id, container_type: 'ProjectPackage',
tiding_type: 'Apply', status: 0).update_all(status: 1)
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
container_id: package.id, container_type: 'ProjectPackage',
tiding_type: 'System', status: 1)
end
end

@ -0,0 +1,31 @@
class ProjectPackages::ApplyPublishService < ApplicationService
Error = Class.new(StandardError)
attr_reader :package
def initialize(package)
@package = package
end
def call
return if package.applying?
raise Error, '该状态下不能申请发布' unless package.may_apply?
ActiveRecord::Base.transaction do
package.apply!
package.project_package_applies.create!
send_project_package_apply_notify!
end
end
private
def send_project_package_apply_notify!
Tiding.create!(user_id: 1, trigger_user_id: package.creator_id,
container_id: package.id, container_type: 'ProjectPackage',
tiding_type: 'Apply', status: 0)
end
end

@ -0,0 +1,29 @@
class ProjectPackages::BiddingService < ApplicationService
Error = Class.new(StandardError)
attr_reader :package, :user
def initialize(package, user)
@package = package
@user = user
end
def call
raise Error, '竞标已截止' if package.bidding_end?
raise Error, '不能参与自己发布的竞标' if package.creator_id == user.id
raise Error, '您已参与竞标' if package.bidding_users.exists?(user_id: user.id)
ActiveRecord::Base.transaction do
package.bidding_users.create!(user_id: user.id)
send_bidding_notify!
end
end
private
def send_bidding_notify!
Tiding.create!(user_id: package.creator_id, trigger_user_id: user.id,
container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'Bidding')
end
end

@ -0,0 +1,26 @@
class ProjectPackages::EndBiddingService < ApplicationService
attr_reader :package
def initialize(package)
@package = package
end
def call
return unless package_deadline?
package.end_bidding!
send_bidding_end_notify!
end
private
def send_bidding_end_notify!
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'BiddingEnd')
end
def package_deadline?
package.may_end_bidding? && package.deadline_at < Time.now
end
end

@ -0,0 +1,38 @@
class ProjectPackages::RefuseApplyService < ApplicationService
Error = Class.new(StandardError)
attr_reader :apply, :package, :params
def initialize(apply, params)
@apply = apply
@package = apply.project_package
@params = params
end
def call
raise Error, '该状态下不能进行此操作' unless apply.may_refuse? && package.may_refuse?
ActiveRecord::Base.transaction do
apply.refuse
apply.reason = params[:reason].to_s.strip
apply.save!
# 发布
package.refuse!
# 消息
send_refuse_notify!
end
end
private
def send_refuse_notify!
Tiding.where(container_id: package.id, container_type: 'ProjectPackage',
tiding_type: 'Apply', status: 0).update_all(status: 1)
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
container_id: package.id, container_type: 'ProjectPackage',
tiding_type: 'System', status: 2, extra: apply.reason)
end
end

@ -0,0 +1,79 @@
class ProjectPackages::SaveService < ApplicationService
Error = Class.new(StandardError)
attr_reader :package, :params
def initialize(package, params)
@package = package
@params = params
end
def call
ProjectPackages::SaveForm.new(params).validate!
check_code_valid! if need_check_code?
is_create = package.new_record?
raise Error, '类型不存在' unless ProjectPackageCategory.where(id: params[:category_id]).exists?
params[:project_package_category_id] = params[:category_id].to_i
raise Error, '竞标截止时间不能小于当前时间' if params[:deadline_at].present? && params[:deadline_at].to_time < Time.now
if params[:min_price].blank? && params[:max_price].present?
params[:min_price] = params[:max_price]
params[:max_price] = nil
end
ActiveRecord::Base.transaction do
package.assign_attributes(params)
package.save!
# 处理附件
deal_attachments
send_create_notify! if is_create
ProjectPackages::ApplyPublishService.call(package) if with_publish?
end
package
rescue ProjectPackages::ApplyPublishService::Error => ex
raise Error, ex.message
end
private
def need_check_code?
(package.new_record? && params[:contact_phone] != package.creator.phone) ||
(!package.new_record? && package.contact_phone != params[:contact_phone])
end
def check_code_valid!
raise Error, '验证码不能为空' if params[:code].blank?
code = VerificationCode.where(phone: params[:contact_phone], code_type: 9, code: params[:code]).last
raise Error, '无效的验证码' if code.blank? || !code.valid_code?
end
def deal_attachments
attachment_ids = Array.wrap(params[:attachment_ids]).compact.map(&:to_i) || []
old_attachment_ids = package.attachments.pluck(:id)
destroy_ids = old_attachment_ids - attachment_ids
package.attachments.where(id: destroy_ids).delete_all
new_ids = attachment_ids - old_attachment_ids
if new_ids.present?
Attachment.where(id: new_ids, container_id: nil).update_all(container_id: package.id, container_type: 'ProjectPackage')
end
end
def send_create_notify!
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'Created')
end
def with_publish?
params[:publish].to_s == 'true'
end
end

@ -0,0 +1,50 @@
class ProjectPackages::WinBiddingService < ApplicationService
Error = Class.new(StandardError)
attr_reader :package, :params
def initialize(package, params)
@package = package
@params = params
end
def call
raise Error, '竞标报名还未结束' unless package.bidding_end?
raise Error, '该状态下不能选择中标者' unless package.may_finish_bidding?
win_user_ids = Array.wrap(params[:user_ids]).compact.map(&:to_i)
bidding_user_ids = package.bidding_users.pluck(:user_id)
win_user_ids = bidding_user_ids & win_user_ids
raise Error, '请选择中标者' if win_user_ids.blank?
ActiveRecord::Base.transaction do
package.finish_bidding!
# win bidding users
package.bidding_users.where(user_id: win_user_ids).update_all(status: :bidding_won)
# lose bidding users
lost_user_ids = bidding_user_ids - win_user_ids
package.bidding_users.where(user_id: lost_user_ids).update_all(status: :bidding_lost)
send_bidding_result_notify!('BiddingWon', win_user_ids)
send_bidding_result_notify!('BiddingLost', lost_user_ids)
end
package
end
private
def send_bidding_result_notify!(type, user_ids)
columns = %i[user_id trigger_user_id container_id container_type tiding_type created_at updated_at]
Tiding.bulk_insert(*columns) do |worker|
base_attr = { trigger_user_id: package.creator_id, container_id: package.id,
container_type: 'ProjectPackage', tiding_type: type }
user_ids.each do |user_id|
worker.add(base_attr.merge(user_id: user_id))
end
end
end
end

@ -0,0 +1,12 @@
class CheckProjectPackageDeadlineTask
def call
ProjectPackage.where(status: :published).where('deadline_at < ?', Time.now).find_each do |package|
begin
ProjectPackages::EndBiddingService.new(package).call
rescue => ex
Rails.logger.error ex.message
Rails.logger.error ex.backtrace.join('\n')
end
end
end
end

@ -7,7 +7,7 @@ json.group_list do
end end
end end
# 未分班展示情况放在最后 # 未分班展示情况放在最后
if @course_groups.count != (@page -1)*@limit.to_i && @course_groups.count < @limit.to_i if @course_groups.count != (@page.to_i - 1)*@limit.to_i && @course_groups.count < @limit.to_i
ungroup_work_count = homework_ungroup_works_count(@homework, @ungroup_user_ids) ungroup_work_count = homework_ungroup_works_count(@homework, @ungroup_user_ids)
if ungroup_work_count > 0 if ungroup_work_count > 0
json.ungroup_list do json.ungroup_list do

@ -0,0 +1,13 @@
json.count @count
json.project_packages do
json.array! @packages.each do |package|
json.extract! package, :id, :title, :content, :category_name, :status,
:visit_count, :bidding_users_count, :min_price, :max_price
json.category_id package.project_package_category_id
json.updated_at package.display_updated_at
json.deadline_at package.display_deadline_at
json.published_at package.display_published_at
end
end

@ -0,0 +1,43 @@
package = current_package
json.extract! package, :id, :title, :content, :category_name, :status,
:visit_count, :bidding_users_count, :min_price, :max_price
json.category_id package.project_package_category_id
# 只有自己和管理员才返回私人信息
if package_manageable?
json.contact_name package.contact_name
json.contact_phone package.contact_phone
end
json.updated_at package.display_updated_at
json.deadline_at package.display_deadline_at
json.published_at package.display_published_at
json.creator do
json.partial! 'users/user_simple', user: package.creator
end
json.attachments do
json.array! package.attachments, partial: 'attachments/attachment_simple', as: :attachment
end
json.bidding_users do
json.array! package.bidding_users.includes(:user).each do |bidding_user|
json.partial! 'users/user_simple', user: bidding_user.user
json.status bidding_user.status
end
end
json.operation do
if current_user
manageable = package_manageable?
json.can_bidding package.can_bidding?(current_user)
json.can_select_bidding_user package.bidding_end? && package.bidding_ended? && manageable
json.can_edit package.editable? && manageable
json.can_delete package.deletable? && manageable
end
end

@ -1,3 +1,3 @@
json.status 1 json.status 1
json.message "发送成功" json.message "发送成功"
json.url "/homework_commons?course=#{@course.id}&homework_type=4" json.url module_url(@course.none_hidden_course_modules.first, @course)

@ -6,7 +6,7 @@ json.subject_score @subject.all_score
json.member_count @subject.member_count json.member_count @subject.member_count
json.allow_delete @is_creator && (@subject.status != 2 || @user.admin?) json.allow_delete @is_creator && (@subject.status != 2 || @user.admin?)
json.publish_status publish_status(@subject, @is_creator, @user) json.publish_status publish_status(@subject, @is_creator, @user, @shixuns)
json.allow_statistics @user.manager_of_subject?(@subject) json.allow_statistics @user.manager_of_subject?(@subject)
json.allow_send @user.logged? json.allow_send @user.logged?
json.allow_visit @user.manager_of_subject?(@subject) || @user.admin? || @subject.status > 1 json.allow_visit @user.manager_of_subject?(@subject) || @user.admin? || @subject.status > 1

@ -0,0 +1,7 @@
if @notice && @notice.end_time > Time.now
json.system_update true
json.system_score @notice.notes.rstrip
json.(@notice, :subject, :start_time, :end_time)
else
json.system_update false
end

@ -0,0 +1,6 @@
'zh-CN':
bidding_user:
status:
pending: 竞标中
bidding_won: 已中标
bidding_lost: 未中标

@ -0,0 +1,13 @@
'zh-CN':
activemodel:
attributes:
project_packages/save_form:
category_id: 类型
title: 标题
content: 描述
deadline_at: 截止日期
min_price: 最小价格
max_price: 最大价格
contact_name: 联系人姓名
contact_phone: 联系人电话
code: 验证码

@ -0,0 +1,9 @@
zh-CN:
project_package:
status:
pending: 已创建
applying: 审核中
refused: 已拒绝
published: 竞标中
bidding_ended: 待选标
bidding_finished: 已完成

@ -64,6 +64,7 @@ Rails.application.routes.draw do
get :search_user_projects get :search_user_projects
post :brief_introduction post :brief_introduction
post :attendance post :attendance
get :system_update
resource :trial_apply, only: [:create] resource :trial_apply, only: [:create]
resources :projects, only: [] do resources :projects, only: [] do
@ -682,6 +683,13 @@ Rails.application.routes.draw do
resources :students, only: [:index] resources :students, only: [:index]
end end
end end
resources :project_package_categories, only: [:index]
resources :project_packages, only: [:index, :show, :create, :update, :destroy] do
resources :bidding_users, only: [:create] do
post :win, on: :collection
end
end
end end
#git 认证回调 #git 认证回调

@ -1,5 +1,5 @@
class AddTsMemForOutputs < ActiveRecord::Migration[5.2] class AddTsMemForOutputs < ActiveRecord::Migration[5.2]
def change def change
add_column :outputs, :ts_mem, :float #add_column :outputs, :ts_mem, :float
end end
end end

@ -1,5 +1,5 @@
class AddTsTimeForOutputs < ActiveRecord::Migration[5.2] class AddTsTimeForOutputs < ActiveRecord::Migration[5.2]
def change def change
add_column :outputs, :ts_time, :float #add_column :outputs, :ts_time, :float
end end
end end

@ -0,0 +1,11 @@
class ModifyAnswerForChallenges < ActiveRecord::Migration[5.2]
def change
challenges = Challenge.where("answer is not null or answer != ''")
.includes(:challenge_answers).unscoped
challenges.find_each do |c|
next if c.challenge_answers.present?
puts("############challenge_id:##{c.id}")
ChallengeAnswer.create(name: "解题代码", contents: "#{c.answer}", level: 1, score: 100, challenge_id: c.id)
end
end
end

@ -1,66 +1,62 @@
<!DOCTYPE html> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html> <html>
<head> <head>
<title>We're sorry, but something went wrong (500)</title> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <title>EduCoder 500 error</title>
<style> <link type="text/css" rel="stylesheet" href="/stylesheets/educoder/edu-main.css" />
.rails-default-error-page { <link href="//at.alicdn.com/t/font_653600_rr8l5v2aaym.css" rel="stylesheet" type="text/css"/>
background-color: #EFEFEF; <script src="/javascripts/jquery-1.8.3-ui-1.9.2-ujs-2.0.3.js"></script>
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
margin: 0;
}
.rails-default-error-page div.dialog { <style type="text/css">
width: 95%; body {
max-width: 33em; font-family: "微软雅黑","宋体";
margin: 4em auto 0; background: #fff;
} }
h1 {
.rails-default-error-page div.dialog > div { font-size: 1.5em;
border: 1px solid #CCC; }
border-right-color: #999; p {
border-left-color: #999; font-size: 0.8em;
border-bottom-color: #BBB; }
border-top: #B00100 solid 4px; .h_content{
border-top-left-radius: 9px; text-align: center;
border-top-right-radius: 9px; padding-top: 15px;
background-color: white; }
padding: 7px 12% 0; .font_h{
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17); font-size: 24px;
} color: #ff0077;
}
.rails-default-error-page h1 { .verticalCenter{
font-size: 100%; height: 100%;
color: #730E15; justify-content: center;
line-height: 1.5em; align-items: center;
} display: -webkit-flex;
}
.rails-default-error-page div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
</style> </style>
<script type="text/javascript">
$(function(){
if(window.history.length == 1)
{
$("#history_back").css("color","#CCC");
$("#history_back").css("cursor","default");
}
});
</script>
</head> </head>
<body>
<body class="rails-default-error-page"> <!-- <h1>Internal error</h1>
<!-- This file lives in public/500.html --> <p>An error occurred on the page you were trying to access.<br />
<div class="dialog"> If you continue to experience problems please contact your Trustie administrator for assistance.</p>
<div> <p>If you are the Trustie administrator, check your log files for details about the error.</p> -->
<h1>We're sorry, but something went wrong.</h1> <div class="verticalCenter">
</div> <div class="edu-txt-center">
<p>If you are the application owner check the logs for more information.</p> <img src="/images/warn/pic_500.jpg" >
<p class="font-18 mt40">您可以稍后尝试&nbsp;
您可以稍后尝试&nbsp;<a href="javascript:history.back()" class="color-blue">返回上页</a>,或者&nbsp;
<a target="_blank" href="//shang.qq.com/wpa/qunwpa?idkey=2f2043d88c1bd61d182b98bf1e061c6185e23055bec832c07d8148fe11c5a6cd" class="color-blue">QQ反馈>></a>
</p>
</div> </div>
</div>
</body> </body>
</html> </html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because it is too large Load Diff

@ -9,7 +9,7 @@ const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter'); const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
// const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const getClientEnvironment = require('./env'); const getClientEnvironment = require('./env');
const paths = require('./paths'); const paths = require('./paths');
@ -28,8 +28,8 @@ const env = getClientEnvironment(publicUrl);
// The production configuration is different and lives in a separate file. // The production configuration is different and lives in a separate file.
module.exports = { module.exports = {
// You may want 'eval' instead if you prefer to see the compiled output in DevTools. // You may want 'eval' instead if you prefer to see the compiled output in DevTools.
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343. // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s
devtool: 'cheap-module-source-map', devtool: "eval", // 开启调试
// These are the "entry points" to our application. // These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle. // This means they will be the "root" imports that are included in JS bundle.
// The first two entry points enable "hot" CSS and auto-refreshes for JS. // The first two entry points enable "hot" CSS and auto-refreshes for JS.
@ -113,7 +113,6 @@ module.exports = {
// First, run the linter. // First, run the linter.
// It's important to do this before Babel processes the JS. // It's important to do this before Babel processes the JS.
// 上线然后要注释回来
// { // {
// test: /\.(js|jsx|mjs)$/, // test: /\.(js|jsx|mjs)$/,
// enforce: 'pre', // enforce: 'pre',
@ -250,7 +249,8 @@ module.exports = {
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack // https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js: // You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
], new MonacoWebpackPlugin(),
],
// Some libraries import Node modules but don't use them in the browser. // Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works. // Tell Webpack to provide empty mocks for them so importing them works.
node: { node: {

@ -1,349 +1,365 @@
'use strict'; 'use strict';
const autoprefixer = require('autoprefixer'); const autoprefixer = require('autoprefixer');
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin'); const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter'); const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const paths = require('./paths'); const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
const getClientEnvironment = require('./env');
const paths = require('./paths');
// Webpack uses `publicPath` to determine where the app is being served from. const getClientEnvironment = require('./env');
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath; // Webpack uses `publicPath` to determine where the app is being served from.
// Some apps do not use client-side routing with pushState. // It requires a trailing slash, or the file assets will get an incorrect path.
// For these, "homepage" can be set to "." to enable relative asset paths. const publicPath = paths.servedPath;
const shouldUseRelativeAssetPaths = publicPath === './'; // Some apps do not use client-side routing with pushState.
// Source maps are resource heavy and can cause out of memory issue for large source files. // For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; const shouldUseRelativeAssetPaths = publicPath === './';
// `publicUrl` is just like `publicPath`, but we will provide it to our app // Source maps are resource heavy and can cause out of memory issue for large source files.
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. // `publicUrl` is just like `publicPath`, but we will provide it to our app
const publicUrl = publicPath.slice(0, -1); // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Get environment variables to inject into our app. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const env = getClientEnvironment(publicUrl); const publicUrl = publicPath.slice(0, -1);
// Get environment variables to inject into our app.
// Assert this just to be safe. const env = getClientEnvironment(publicUrl);
// Development builds of React are slow and not intended for production.
if (env.stringified['process.env'].NODE_ENV !== '"production"') { // Assert this just to be safe.
throw new Error('Production builds must have NODE_ENV=production.'); // Development builds of React are slow and not intended for production.
} if (env.stringified['process.env'].NODE_ENV !== '"production"') {
throw new Error('Production builds must have NODE_ENV=production.');
// Note: defined here because it will be used more than once. }
const cssFilename = './static/css/[name].[contenthash:8].css';
// Note: defined here because it will be used more than once.
// ExtractTextPlugin expects the build output to be flat. const cssFilename = './static/css/[name].[contenthash:8].css';
// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
// However, our output is structured with css, js and media folders. // ExtractTextPlugin expects the build output to be flat.
// To have this structure working with relative paths, we have to use custom options. // (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
const extractTextPluginOptions = shouldUseRelativeAssetPaths // However, our output is structured with css, js and media folders.
? // Making sure that the publicPath goes back to to build folder. // To have this structure working with relative paths, we have to use custom options.
{ publicPath: Array(cssFilename.split('/').length).join('../') } const extractTextPluginOptions = shouldUseRelativeAssetPaths
: {}; ? // Making sure that the publicPath goes back to to build folder.
{ publicPath: Array(cssFilename.split('/').length).join('../') }
// This is the production configuration. : {};
// It compiles slowly and is focused on producing a fast and minimal bundle.
// The development configuration is different and lives in a separate file. // This is the production configuration.
// It compiles slowly and is focused on producing a fast and minimal bundle.
// console.log('publicPath ', publicPath) // The development configuration is different and lives in a separate file.
module.exports = {
// Don't attempt to continue if there are any errors. // console.log('publicPath ', publicPath)
bail: true, module.exports = {
// We generate sourcemaps in production. This is slow but gives good results. // Don't attempt to continue if there are any errors.
// You can exclude the *.map files from the build during deployment. bail: true,
// devtool: shouldUseSourceMap ? 'nosources-source-map' : false, //正式版 // We generate sourcemaps in production. This is slow but gives good results.
devtool: shouldUseSourceMap ? 'source-map' : false,//测试版 // You can exclude the *.map files from the build during deployment.
// In production, we only want to load the polyfills and the app code. // devtool: shouldUseSourceMap ? 'nosources-source-map' : false, //正式版
entry: [require.resolve('./polyfills'), paths.appIndexJs], devtool: shouldUseSourceMap ? 'source-map' : false,//测试版
output: { // In production, we only want to load the polyfills and the app code.
// The build folder. entry: [require.resolve('./polyfills'), paths.appIndexJs],
path: paths.appBuild, output: {
// Generated JS file names (with nested folders). // The build folder.
// There will be one main bundle, and one file per asynchronous chunk. path: paths.appBuild,
// We don't currently advertise code splitting but Webpack supports it. // Generated JS file names (with nested folders).
filename: './static/js/[name].[chunkhash:8].js', // There will be one main bundle, and one file per asynchronous chunk.
chunkFilename: './static/js/[name].[chunkhash:8].chunk.js', // We don't currently advertise code splitting but Webpack supports it.
// We inferred the "public path" (such as / or /my-project) from homepage. filename: './static/js/[name].[chunkhash:8].js',
// cdn chunkFilename: './static/js/[name].[chunkhash:8].chunk.js',
// publicPath: 'https://shixun.educoder.net/react/build/', //publicPath, https://cdn.educoder.net // We inferred the "public path" (such as / or /my-project) from homepage.
// publicPath: 'https://cdn-testeduplus2.educoder.net/react/build/', //publicPath, https://cdn.educoder.net // cdn
publicPath: '/react/build/', //publicPath, https://cdn.educoder.net // publicPath: 'https://shixun.educoder.net/react/build/', //publicPath, https://cdn.educoder.net
// publicPath: 'https://cdn-testeduplus2.educoder.net/react/build/', //publicPath, https://cdn.educoder.net
// Point sourcemap entries to original disk location (format as URL on Windows) publicPath: '/react/build/', //publicPath, https://cdn.educoder.net
devtoolModuleFilenameTemplate: info =>
path // Point sourcemap entries to original disk location (format as URL on Windows)
.relative(paths.appSrc, info.absoluteResourcePath) devtoolModuleFilenameTemplate: info =>
.replace(/\\/g, '/'), path
}, .relative(paths.appSrc, info.absoluteResourcePath)
resolve: { .replace(/\\/g, '/'),
// This allows you to set a fallback for where Webpack should look for modules. },
// We placed these paths second because we want `node_modules` to "win" resolve: {
// if there are any conflicts. This matches Node resolution mechanism. // This allows you to set a fallback for where Webpack should look for modules.
// https://github.com/facebookincubator/create-react-app/issues/253 // We placed these paths second because we want `node_modules` to "win"
modules: ['node_modules', paths.appNodeModules].concat( // if there are any conflicts. This matches Node resolution mechanism.
// It is guaranteed to exist because we tweak it in `env.js` // https://github.com/facebookincubator/create-react-app/issues/253
process.env.NODE_PATH.split(path.delimiter).filter(Boolean) modules: ['node_modules', paths.appNodeModules].concat(
), // It is guaranteed to exist because we tweak it in `env.js`
// These are the reasonable defaults supported by the Node ecosystem. process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
// We also include JSX as a common component filename extension to support ),
// some tools, although we do not recommend using it, see: // These are the reasonable defaults supported by the Node ecosystem.
// https://github.com/facebookincubator/create-react-app/issues/290 // We also include JSX as a common component filename extension to support
// `web` extension prefixes have been added for better support // some tools, although we do not recommend using it, see:
// for React Native Web. // https://github.com/facebookincubator/create-react-app/issues/290
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'], // `web` extension prefixes have been added for better support
alias: { // for React Native Web.
"educoder": __dirname + "/../src/common/educoder.js", extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
// Support React Native Web alias: {
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/ "educoder": __dirname + "/../src/common/educoder.js",
'react-native': 'react-native-web', // Support React Native Web
}, // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
plugins: [ 'react-native': 'react-native-web',
// Prevents users from importing files from outside of src/ (or node_modules/). },
// This often causes confusion because we only process files within src/ with babel. plugins: [
// To fix this, we prevent you from importing files out of src/ -- if you'd like to, // Prevents users from importing files from outside of src/ (or node_modules/).
// please link the files into your node_modules/ and let module-resolution kick in. // This often causes confusion because we only process files within src/ with babel.
// Make sure your source files are compiled, as they will not be processed in any way. // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), // please link the files into your node_modules/ and let module-resolution kick in.
], // Make sure your source files are compiled, as they will not be processed in any way.
}, new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
module: { ],
strictExportPresence: true, },
rules: [ module: {
// TODO: Disable require.ensure as it's not a standard language feature. strictExportPresence: true,
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. rules: [
// { parser: { requireEnsure: false } }, // TODO: Disable require.ensure as it's not a standard language feature.
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
// First, run the linter. // { parser: { requireEnsure: false } },
// It's important to do this before Babel processes the JS.
// { // First, run the linter.
// test: /\.(js|jsx|mjs)$/, // It's important to do this before Babel processes the JS.
// enforce: 'pre', {
// use: [ test: /\.(js|jsx|mjs)$/,
// { enforce: 'pre',
// options: { use: [
// formatter: eslintFormatter, {
// eslintPath: require.resolve('eslint'), options: {
// formatter: eslintFormatter,
// }, eslintPath: require.resolve('eslint'),
// loader: require.resolve('eslint-loader'),
// }, },
// ], loader: require.resolve('eslint-loader'),
// include: paths.appSrc, },
// }, ],
{ include: paths.appSrc,
// "oneOf" will traverse all following loaders until one will },
// match the requirements. When no loader matches it will fall {
// back to the "file" loader at the end of the loader list. // "oneOf" will traverse all following loaders until one will
oneOf: [ // match the requirements. When no loader matches it will fall
// "url" loader works just like "file" loader but it also embeds // back to the "file" loader at the end of the loader list.
// assets smaller than specified size as data URLs to avoid requests. oneOf: [
{ // "url" loader works just like "file" loader but it also embeds
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], // assets smaller than specified size as data URLs to avoid requests.
loader: require.resolve('url-loader'), {
options: { test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
limit: 10000, loader: require.resolve('url-loader'),
name: 'static/media/[name].[hash:8].[ext]', options: {
}, limit: 10000,
}, name: 'static/media/[name].[hash:8].[ext]',
// Process JS with Babel. },
{ },
test: /\.(js|jsx|mjs)$/, // Process JS with Babel.
include: paths.appSrc, {
loader: require.resolve('babel-loader'), test: /\.(js|jsx|mjs)$/,
options: { include: paths.appSrc,
loader: require.resolve('babel-loader'),
compact: true, options: {
},
}, compact: true,
// The notation here is somewhat confusing. },
// "postcss" loader applies autoprefixer to our CSS. },
// "css" loader resolves paths in CSS and adds assets as dependencies. // The notation here is somewhat confusing.
// "style" loader normally turns CSS into JS modules injecting <style>, // "postcss" loader applies autoprefixer to our CSS.
// but unlike in development configuration, we do something different. // "css" loader resolves paths in CSS and adds assets as dependencies.
// `ExtractTextPlugin` first applies the "postcss" and "css" loaders // "style" loader normally turns CSS into JS modules injecting <style>,
// (second argument), then grabs the result CSS and puts it into a // but unlike in development configuration, we do something different.
// separate file in our build process. This way we actually ship // `ExtractTextPlugin` first applies the "postcss" and "css" loaders
// a single CSS file in production instead of JS code injecting <style> // (second argument), then grabs the result CSS and puts it into a
// tags. If you use code splitting, however, any async bundles will still // separate file in our build process. This way we actually ship
// use the "style" loader inside the async code so CSS from them won't be // a single CSS file in production instead of JS code injecting <style>
// in the main CSS file. // tags. If you use code splitting, however, any async bundles will still
{ // use the "style" loader inside the async code so CSS from them won't be
test: /\.css$/, // in the main CSS file.
loader: ExtractTextPlugin.extract( {
Object.assign( test: /\.css$/,
{ loader: ExtractTextPlugin.extract(
fallback: { Object.assign(
loader: require.resolve('style-loader'), {
options: { fallback: {
hmr: false, loader: require.resolve('style-loader'),
}, options: {
}, hmr: false,
use: [ },
{ },
loader: require.resolve('css-loader'), use: [
options: { {
importLoaders: 1, loader: require.resolve('css-loader'),
minimize: true, options: {
sourceMap: shouldUseSourceMap, importLoaders: 1,
}, minimize: true,
}, sourceMap: shouldUseSourceMap,
{ },
loader: require.resolve('postcss-loader'), },
options: { {
// Necessary for external CSS imports to work loader: require.resolve('postcss-loader'),
// https://github.com/facebookincubator/create-react-app/issues/2677 options: {
ident: 'postcss', // Necessary for external CSS imports to work
plugins: () => [ // https://github.com/facebookincubator/create-react-app/issues/2677
require('postcss-flexbugs-fixes'), ident: 'postcss',
autoprefixer({ plugins: () => [
browsers: [ require('postcss-flexbugs-fixes'),
'>1%', autoprefixer({
'last 4 versions', browsers: [
'Firefox ESR', '>1%',
'not ie < 9', // React doesn't support IE8 anyway 'last 4 versions',
], 'Firefox ESR',
flexbox: 'no-2009', 'not ie < 9', // React doesn't support IE8 anyway
}), ],
], flexbox: 'no-2009',
}, }),
}, ],
], },
}, },
extractTextPluginOptions ],
) },
), extractTextPluginOptions
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`. )
}, ),
// "file" loader makes sure assets end up in the `build` folder. // Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
// When you `import` an asset, you get its filename. },
// This loader doesn't use a "test" so it will catch all modules // "file" loader makes sure assets end up in the `build` folder.
// that fall through the other loaders. // When you `import` an asset, you get its filename.
{ // This loader doesn't use a "test" so it will catch all modules
loader: require.resolve('file-loader'), // that fall through the other loaders.
// Exclude `js` files to keep "css" loader working as it injects {
// it's runtime that would otherwise processed through "file" loader. loader: require.resolve('file-loader'),
// Also exclude `html` and `json` extensions so they get processed // Exclude `js` files to keep "css" loader working as it injects
// by webpacks internal loaders. // it's runtime that would otherwise processed through "file" loader.
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/], // Also exclude `html` and `json` extensions so they get processed
options: { // by webpacks internal loaders.
name: 'static/media/[name].[hash:8].[ext]', exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
}, options: {
}, name: 'static/media/[name].[hash:8].[ext]',
// ** STOP ** Are you adding a new loader? },
// Make sure to add the new loader(s) before the "file" loader. },
], // ** STOP ** Are you adding a new loader?
}, // Make sure to add the new loader(s) before the "file" loader.
], ],
}, },
plugins: [ ],
// Makes some environment variables available in index.html. },
// The public URL is available as %PUBLIC_URL% in index.html, e.g.: plugins: [
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> // Makes some environment variables available in index.html.
// In production, it will be an empty string unless you specify "homepage" // The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// in `package.json`, in which case it will be the pathname of that URL. // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
new InterpolateHtmlPlugin(env.raw), // In production, it will be an empty string unless you specify "homepage"
// Generates an `index.html` file with the <script> injected. // in `package.json`, in which case it will be the pathname of that URL.
new HtmlWebpackPlugin({ new InterpolateHtmlPlugin(env.raw),
inject: true, // Generates an `index.html` file with the <script> injected.
template: paths.appHtml, new HtmlWebpackPlugin({
minify: { inject: true,
removeComments: true, template: paths.appHtml,
collapseWhitespace: true, minify: {
removeRedundantAttributes: true, removeComments: true,
useShortDoctype: true, collapseWhitespace: true,
removeEmptyAttributes: true, removeRedundantAttributes: true,
removeStyleLinkTypeAttributes: true, useShortDoctype: true,
keepClosingSlash: true, removeEmptyAttributes: true,
minifyJS: true, removeStyleLinkTypeAttributes: true,
minifyCSS: true, keepClosingSlash: true,
minifyURLs: true, minifyJS: true,
}, minifyCSS: true,
}), minifyURLs: true,
// Makes some environment variables available to the JS code, for example: },
// if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. }),
// It is absolutely essential that NODE_ENV was set to production here. // Makes some environment variables available to the JS code, for example:
// Otherwise React will be compiled in the very slow development mode. // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`.
new webpack.DefinePlugin(env.stringified), // It is absolutely essential that NODE_ENV was set to production here.
// Minify the code. // Otherwise React will be compiled in the very slow development mode.
new webpack.optimize.UglifyJsPlugin({ new webpack.DefinePlugin(env.stringified),
compress: { // Minify the code.
warnings: false, // new webpack.optimize.UglifyJsPlugin({
// Disabled because of an issue with Uglify breaking seemingly valid code: // compress: {
// https://github.com/facebookincubator/create-react-app/issues/2376 // warnings: false,
// Pending further investigation: // // Disabled because of an issue with Uglify breaking seemingly valid code:
// https://github.com/mishoo/UglifyJS2/issues/2011 // // https://github.com/facebookincubator/create-react-app/issues/2376
comparisons: false, // // Pending further investigation:
}, // // https://github.com/mishoo/UglifyJS2/issues/2011
mangle: { // comparisons: false,
safari10: true, // },
}, // mangle: {
output: { // safari10: true,
comments: false, // },
// Turned on because emoji and regex is not minified properly using default // output: {
// https://github.com/facebookincubator/create-react-app/issues/2488 // comments: false,
ascii_only: true, // // Turned on because emoji and regex is not minified properly using default
}, // // https://github.com/facebookincubator/create-react-app/issues/2488
sourceMap: shouldUseSourceMap, // ascii_only: true,
}), // },
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`. // sourceMap: shouldUseSourceMap,
new ExtractTextPlugin({ // }),
filename: cssFilename, //正式版上线后打开去掉debuger和console
}), new ParallelUglifyPlugin({
// Generate a manifest file which contains a mapping of all asset filenames cacheDir: '.cache/',
// to their corresponding output file so that tools can pick it up without uglifyJS:{
// having to parse `index.html`. output: {
new ManifestPlugin({ comments: false
fileName: 'asset-manifest.json', },
}), warnings: false,
// Generate a service worker script that will precache, and keep up to date, compress: {
// the HTML & assets that are part of the Webpack build. drop_debugger: true,
new SWPrecacheWebpackPlugin({ drop_console: true
// By default, a cache-busting query parameter is appended to requests }
// used to populate the caches, to ensure the responses are fresh. }
// If a URL is already hashed by Webpack, then there is no concern }),
// about it being stale, and the cache-busting can be skipped. // Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
dontCacheBustUrlsMatching: /\.\w{8}\./, new ExtractTextPlugin({
filename: 'service-worker.js', filename: cssFilename,
logger(message) { }),
if (message.indexOf('Total precache size is') === 0) { // Generate a manifest file which contains a mapping of all asset filenames
// This message occurs for every build and is a bit too noisy. // to their corresponding output file so that tools can pick it up without
return; // having to parse `index.html`.
} new ManifestPlugin({
if (message.indexOf('Skipping static resource') === 0) { fileName: 'asset-manifest.json',
// This message obscures real errors so we ignore it. }),
// https://github.com/facebookincubator/create-react-app/issues/2612 // Generate a service worker script that will precache, and keep up to date,
return; // the HTML & assets that are part of the Webpack build.
} new SWPrecacheWebpackPlugin({
// console.log(message); // By default, a cache-busting query parameter is appended to requests
}, // used to populate the caches, to ensure the responses are fresh.
minify: true, // If a URL is already hashed by Webpack, then there is no concern
// For unknown URLs, fallback to the index page // about it being stale, and the cache-busting can be skipped.
navigateFallback: publicUrl + '/index.html', dontCacheBustUrlsMatching: /\.\w{8}\./,
// Ignores URLs starting from /__ (useful for Firebase): filename: 'service-worker.js',
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219 logger(message) {
navigateFallbackWhitelist: [/^(?!\/__).*/], if (message.indexOf('Total precache size is') === 0) {
// Don't precache sourcemaps (they're large) and build asset manifest: // This message occurs for every build and is a bit too noisy.
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/], return;
}), }
// Moment.js is an extremely popular library that bundles large locale files if (message.indexOf('Skipping static resource') === 0) {
// by default due to how Webpack interprets its code. This is a practical // This message obscures real errors so we ignore it.
// solution that requires the user to opt into importing specific locales. // https://github.com/facebookincubator/create-react-app/issues/2612
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack return;
// You can remove this if you don't use Moment.js: }
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // console.log(message);
], },
// Some libraries import Node modules but don't use them in the browser. minify: true,
// Tell Webpack to provide empty mocks for them so importing them works. // For unknown URLs, fallback to the index page
node: { navigateFallback: publicUrl + '/index.html',
dgram: 'empty', // Ignores URLs starting from /__ (useful for Firebase):
fs: 'empty', // https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
net: 'empty', navigateFallbackWhitelist: [/^(?!\/__).*/],
tls: 'empty', // Don't precache sourcemaps (they're large) and build asset manifest:
child_process: 'empty', staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
}, }),
}; // Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
};

@ -1,95 +1,95 @@
'use strict'; 'use strict';
const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware'); const errorOverlayMiddleware = require('react-dev-utils/errorOverlayMiddleware');
const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware'); const noopServiceWorkerMiddleware = require('react-dev-utils/noopServiceWorkerMiddleware');
const ignoredFiles = require('react-dev-utils/ignoredFiles'); const ignoredFiles = require('react-dev-utils/ignoredFiles');
const config = require('./webpack.config.dev'); const config = require('./webpack.config.dev');
const paths = require('./paths'); const paths = require('./paths');
const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const protocol = process.env.HTTPS === 'true' ? 'https' : 'http';
const host = process.env.HOST || '0.0.0.0'; const host = process.env.HOST || '0.0.0.0';
module.exports = function(proxy, allowedHost) { module.exports = function(proxy, allowedHost) {
return { return {
// WebpackDevServer 2.4.3 introduced a security fix that prevents remote // WebpackDevServer 2.4.3 introduced a security fix that prevents remote
// websites from potentially accessing local content through DNS rebinding: // websites from potentially accessing local content through DNS rebinding:
// https://github.com/webpack/webpack-dev-server/issues/887 // https://github.com/webpack/webpack-dev-server/issues/887
// https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a // https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
// However, it made several existing use cases such as development in cloud // However, it made several existing use cases such as development in cloud
// environment or subdomains in development significantly more complicated: // environment or subdomains in development significantly more complicated:
// https://github.com/facebookincubator/create-react-app/issues/2271 // https://github.com/facebookincubator/create-react-app/issues/2271
// https://github.com/facebookincubator/create-react-app/issues/2233 // https://github.com/facebookincubator/create-react-app/issues/2233
// While we're investigating better solutions, for now we will take a // While we're investigating better solutions, for now we will take a
// compromise. Since our WDS configuration only serves files in the `public` // compromise. Since our WDS configuration only serves files in the `public`
// folder we won't consider accessing them a vulnerability. However, if you // folder we won't consider accessing them a vulnerability. However, if you
// use the `proxy` feature, it gets more dangerous because it can expose // use the `proxy` feature, it gets more dangerous because it can expose
// remote code execution vulnerabilities in backends like Django and Rails. // remote code execution vulnerabilities in backends like Django and Rails.
// So we will disable the host check normally, but enable it if you have // So we will disable the host check normally, but enable it if you have
// specified the `proxy` setting. Finally, we let you override it if you // specified the `proxy` setting. Finally, we let you override it if you
// really know what you're doing with a special environment variable. // really know what you're doing with a special environment variable.
disableHostCheck: disableHostCheck:
!proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true', !proxy || process.env.DANGEROUSLY_DISABLE_HOST_CHECK === 'true',
// Enable gzip compression of generated files. // Enable gzip compression of generated files.
compress: true, compress: true,
// Silence WebpackDevServer's own logs since they're generally not useful. // Silence WebpackDevServer's own logs since they're generally not useful.
// It will still show compile warnings and errors with this setting. // It will still show compile warnings and errors with this setting.
clientLogLevel: 'none', clientLogLevel: 'none',
// By default WebpackDevServer serves physical files from current directory // By default WebpackDevServer serves physical files from current directory
// in addition to all the virtual build products that it serves from memory. // in addition to all the virtual build products that it serves from memory.
// This is confusing because those files wont automatically be available in // This is confusing because those files wont automatically be available in
// production build folder unless we copy them. However, copying the whole // production build folder unless we copy them. However, copying the whole
// project directory is dangerous because we may expose sensitive files. // project directory is dangerous because we may expose sensitive files.
// Instead, we establish a convention that only files in `public` directory // Instead, we establish a convention that only files in `public` directory
// get served. Our build script will copy `public` into the `build` folder. // get served. Our build script will copy `public` into the `build` folder.
// In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%: // In `index.html`, you can get URL of `public` folder with %PUBLIC_URL%:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> // <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In JavaScript code, you can access it with `process.env.PUBLIC_URL`. // In JavaScript code, you can access it with `process.env.PUBLIC_URL`.
// Note that we only recommend to use `public` folder as an escape hatch // Note that we only recommend to use `public` folder as an escape hatch
// for files like `favicon.ico`, `manifest.json`, and libraries that are // for files like `favicon.ico`, `manifest.json`, and libraries that are
// for some reason broken when imported through Webpack. If you just want to // for some reason broken when imported through Webpack. If you just want to
// use an image, put it in `src` and `import` it from JavaScript instead. // use an image, put it in `src` and `import` it from JavaScript instead.
contentBase: paths.appPublic, contentBase: paths.appPublic,
// By default files from `contentBase` will not trigger a page reload. // By default files from `contentBase` will not trigger a page reload.
watchContentBase: true, watchContentBase: true,
// Enable hot reloading server. It will provide /sockjs-node/ endpoint // Enable hot reloading server. It will provide /sockjs-node/ endpoint
// for the WebpackDevServer client so it can learn when the files were // for the WebpackDevServer client so it can learn when the files were
// updated. The WebpackDevServer client is included as an entry point // updated. The WebpackDevServer client is included as an entry point
// in the Webpack development configuration. Note that only changes // in the Webpack development configuration. Note that only changes
// to CSS are currently hot reloaded. JS changes will refresh the browser. // to CSS are currently hot reloaded. JS changes will refresh the browser.
hot: true, hot: true,
// It is important to tell WebpackDevServer to use the same "root" path // It is important to tell WebpackDevServer to use the same "root" path
// as we specified in the config. In development, we always serve from /. // as we specified in the config. In development, we always serve from /.
publicPath: config.output.publicPath, publicPath: config.output.publicPath,
// WebpackDevServer is noisy by default so we emit custom message instead // WebpackDevServer is noisy by default so we emit custom message instead
// by listening to the compiler events with `compiler.plugin` calls above. // by listening to the compiler events with `compiler.plugin` calls above.
quiet: true, quiet: true,
// Reportedly, this avoids CPU overload on some systems. // Reportedly, this avoids CPU overload on some systems.
// https://github.com/facebookincubator/create-react-app/issues/293 // https://github.com/facebookincubator/create-react-app/issues/293
// src/node_modules is not ignored to support absolute imports // src/node_modules is not ignored to support absolute imports
// https://github.com/facebookincubator/create-react-app/issues/1065 // https://github.com/facebookincubator/create-react-app/issues/1065
watchOptions: { watchOptions: {
ignored: ignoredFiles(paths.appSrc), ignored: ignoredFiles(paths.appSrc),
}, },
// Enable HTTPS if the HTTPS environment variable is set to 'true' // Enable HTTPS if the HTTPS environment variable is set to 'true'
https: protocol === 'https', https: protocol === 'https',
host: host, host: host,
overlay: false, overlay: false,
historyApiFallback: { historyApiFallback: {
// Paths with dots should still use the history fallback. // Paths with dots should still use the history fallback.
// See https://github.com/facebookincubator/create-react-app/issues/387. // See https://github.com/facebookincubator/create-react-app/issues/387.
disableDotRule: true, disableDotRule: true,
}, },
public: allowedHost, public: allowedHost,
proxy, proxy,
before(app) { before(app) {
// This lets us open files from the runtime error overlay. // This lets us open files from the runtime error overlay.
app.use(errorOverlayMiddleware()); app.use(errorOverlayMiddleware());
// This service worker file is effectively a 'no-op' that will reset any // This service worker file is effectively a 'no-op' that will reset any
// previous service worker registered for the same host:port combination. // previous service worker registered for the same host:port combination.
// We do this in development to avoid hitting the production cache if // We do this in development to avoid hitting the production cache if
// it used the same host and port. // it used the same host and port.
// https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432 // https://github.com/facebookincubator/create-react-app/issues/2272#issuecomment-302832432
app.use(noopServiceWorkerMiddleware()); app.use(noopServiceWorkerMiddleware());
}, },
}; };
}; };

File diff suppressed because it is too large Load Diff

@ -3,9 +3,9 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@flatten/array": "^1.1.7",
"@icedesign/base": "^0.2.5", "@icedesign/base": "^0.2.5",
"antd": "^3.6.5", "@novnc/novnc": "^1.1.0",
"antd": "^3.20.1",
"array-flatten": "^2.1.2", "array-flatten": "^2.1.2",
"autoprefixer": "7.1.6", "autoprefixer": "7.1.6",
"axios": "^0.18.0", "axios": "^0.18.0",
@ -39,14 +39,15 @@
"fs-extra": "3.0.1", "fs-extra": "3.0.1",
"html-webpack-plugin": "2.29.0", "html-webpack-plugin": "2.29.0",
"immutability-helper": "^2.6.6", "immutability-helper": "^2.6.6",
"install": "^0.12.2",
"jest": "20.0.4", "jest": "20.0.4",
"js-file-download": "^0.4.7",
"lodash": "^4.17.5", "lodash": "^4.17.5",
"loglevel": "^1.6.1", "loglevel": "^1.6.1",
"material-ui": "^1.0.0-beta.40", "material-ui": "^1.0.0-beta.40",
"moment": "^2.23.0", "moment": "^2.23.0",
"monaco-editor": "^0.15.6", "monaco-editor": "^0.15.6",
"monaco-editor-webpack-plugin": "^1.7.0", "monaco-editor-webpack-plugin": "^1.7.0",
"npm": "^6.10.1",
"object-assign": "4.1.1", "object-assign": "4.1.1",
"postcss-flexbugs-fixes": "3.2.0", "postcss-flexbugs-fixes": "3.2.0",
"postcss-loader": "2.0.8", "postcss-loader": "2.0.8",
@ -60,13 +61,13 @@
"rc-select": "^8.0.12", "rc-select": "^8.0.12",
"rc-tree": "^1.7.11", "rc-tree": "^1.7.11",
"rc-upload": "^2.5.1", "rc-upload": "^2.5.1",
"react": "^16.3.0", "react": "^16.8.0",
"react-beautiful-dnd": "^10.0.4", "react-beautiful-dnd": "^10.0.4",
"react-codemirror": "^1.0.0", "react-codemirror": "^1.0.0",
"react-codemirror2": "^6.0.0", "react-codemirror2": "^6.0.0",
"react-content-loader": "^3.1.1", "react-content-loader": "^3.1.1",
"react-dev-utils": "^5.0.0", "react-dev-utils": "^5.0.0",
"react-dom": "^16.3.0-alpha.2", "react-dom": "^16.8.0",
"react-hot-loader": "^4.0.0", "react-hot-loader": "^4.0.0",
"react-infinite-scroller": "^1.2.4", "react-infinite-scroller": "^1.2.4",
"react-loadable": "^5.3.1", "react-loadable": "^5.3.1",
@ -86,11 +87,12 @@
"webpack": "3.8.1", "webpack": "3.8.1",
"webpack-dev-server": "2.9.4", "webpack-dev-server": "2.9.4",
"webpack-manifest-plugin": "1.3.2", "webpack-manifest-plugin": "1.3.2",
"webpack-parallel-uglify-plugin": "^1.1.0",
"whatwg-fetch": "2.0.3", "whatwg-fetch": "2.0.3",
"wrap-md-editor": "^0.2.20" "wrap-md-editor": "^0.2.20"
}, },
"scripts": { "scripts": {
"start": "node --max_old_space_size=8072 scripts/start.js", "start": "node --max_old_space_size=15360 scripts/start.js",
"build": "node --max_old_space_size=15360 scripts/build.js", "build": "node --max_old_space_size=15360 scripts/build.js",
"concat": "node scripts/concat.js", "concat": "node scripts/concat.js",
"gen_stats": "NODE_ENV=production webpack --profile --config=./config/webpack.config.prod.js --json > stats.json", "gen_stats": "NODE_ENV=production webpack --profile --config=./config/webpack.config.prod.js --json > stats.json",
@ -157,11 +159,10 @@
"port": "3007", "port": "3007",
"devDependencies": { "devDependencies": {
"@babel/runtime": "7.0.0-beta.51", "@babel/runtime": "7.0.0-beta.51",
"antd": "^3.6.5",
"babel-plugin-import": "^1.11.0", "babel-plugin-import": "^1.11.0",
"concat": "^1.0.3", "concat": "^1.0.3",
"happypack": "^5.0.1", "happypack": "^5.0.1",
"videojs-for-react": "^0.0.3", "webpack-bundle-analyzer": "^3.0.3",
"webpack-bundle-analyzer": "^3.0.3" "webpack-parallel-uglify-plugin": "^1.1.0"
} }
} }

@ -1799,7 +1799,20 @@ ol.linenums{margin-top:0;margin-bottom:0}li.L1,li.L3,li.L5,li.L7,li.L9{backgroun
.page--body { .page--body {
margin-top: 54px; margin-top: 54px;
} }
#myshixun_top {
display: flex;
height: 54px;
}
.yslflexhome {
display: flex;
flex-direction: row;
}
.yslflexhomes {
display: flex;
flex-direction: row;
}
#games_repository_contents, #games_repository_contents,
.cm-s-railscasts .CodeMirror-gutters, .cm-s-railscasts .CodeMirror-gutters,
.split-panel--second { .split-panel--second {

@ -296,7 +296,7 @@ label.infolabel{display: block;float: left;width: 56px;text-align: right;margin-
.subshaicontent a{float: left;margin-right: 20px;color: #999;cursor: pointer} .subshaicontent a{float: left;margin-right: 20px;color: #999;cursor: pointer}
.search-new{width: 248px;height:32px;position: relative} .search-new{width: 248px;height:32px;position: relative;margin-right: 35px;}
.search-span{display: block;position: absolute;width: 100%;height: 100%;left:0px;top:0px;background-color: #F4F4F4;border: 1px solid #EAEAEA; border-radius: 4px;z-index: 1} .search-span{display: block;position: absolute;width: 100%;height: 100%;left:0px;top:0px;background-color: #F4F4F4;border: 1px solid #EAEAEA; border-radius: 4px;z-index: 1}
.search-new-input{height: 32px;padding-left: 5px;width: 225px;border: none;box-sizing: border-box;background: none;outline: none;position: absolute;left:0px;top:1px;z-index: 2} .search-new-input{height: 32px;padding-left: 5px;width: 225px;border: none;box-sizing: border-box;background: none;outline: none;position: absolute;left:0px;top:1px;z-index: 2}
.search-new img,.search-new a,.search-new .searchicon{cursor: pointer;position: absolute;right:2px;top:2px;z-index: 2} .search-new img,.search-new a,.search-new .searchicon{cursor: pointer;position: absolute;right:2px;top:2px;z-index: 2}
@ -455,7 +455,7 @@ li.li-width7{width: 7%;text-align: left}
.top-black-trangle{display: block;border-width: 8px;position: absolute;top: -16px;right: 4px;border-style: dashed solid dashed dashed;border-color: transparent transparent rgba(5,16,26,0.6) transparent;font-size: 0;line-height: 0;} .top-black-trangle{display: block;border-width: 8px;position: absolute;top: -16px;right: 4px;border-style: dashed solid dashed dashed;border-color: transparent transparent rgba(5,16,26,0.6) transparent;font-size: 0;line-height: 0;}
.right-black-trangle{display: block;border-width: 8px;position: absolute;top: 10px;right: -16px;border-style: dashed solid dashed dashed;border-color: transparent transparent transparent rgba(5,16,26,0.6);font-size: 0;line-height: 0;} .right-black-trangle{display: block;border-width: 8px;position: absolute;top: 10px;right: -16px;border-style: dashed solid dashed dashed;border-color: transparent transparent transparent rgba(5,16,26,0.6);font-size: 0;line-height: 0;}
.activity-nav.active{color: #4CACFF!important;} .activity-nav.active{color: #4CACFF!important;}
.yslinvitetip{right: 140px;color: #fff; box-sizing: border-box;width: 170px;text-align: center;border-radius: 2px;background-color: rgba(5,16,26,0.6)}
.courseNewNum{display: block;background: #FF6800;border-radius:30px;padding:0px 2px;color: #fff!important;font-size: 11px; .courseNewNum{display: block;background: #FF6800;border-radius:30px;padding:0px 2px;color: #fff!important;font-size: 11px;
height: 16px;line-height: 15px;min-width: 12px;text-align: center;margin-top: 17px;} height: 16px;line-height: 15px;min-width: 12px;text-align: center;margin-top: 17px;}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

@ -19,7 +19,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>Educoder</title> <title>EduCoder</title>
<script type="text/javascript"> <script type="text/javascript">
window.__isR = true; window.__isR = true;
@ -31,7 +31,7 @@
location.pathname.indexOf("/compatibility") == -1) { location.pathname.indexOf("/compatibility") == -1) {
// location.href = './compatibility' // location.href = './compatibility'
location.href = './compatibility.html' location.href = '/compatibility.html'
} }
</script> </script>
@ -55,7 +55,7 @@
<!-- <link href="/react/build/css/iconfont.css" rel="stylesheet" type="text/css"> --> <!-- <link href="/react/build/css/iconfont.css" rel="stylesheet" type="text/css"> -->
<link href="https://testeduplus2.educoder.net/stylesheets/educoder/edu-all.css" rel="stylesheet" type="text/css"> <!--<link href="http://47.96.87.25:48080/stylesheets/educoder/edu-all.css" rel="stylesheet" type="text/css">-->
<!--<link href="https://pandao.github.io/editor.md/examples/css/style.css" rel="stylesheet" type="text/css">--> <!--<link href="https://pandao.github.io/editor.md/examples/css/style.css" rel="stylesheet" type="text/css">-->
@ -67,7 +67,9 @@
<!-- <link rel="stylesheet" type="text/css" href="https://www.educoder.net/stylesheets/css/font-awesome.css?1510652321"> --> <!-- <link rel="stylesheet" type="text/css" href="https://www.educoder.net/stylesheets/css/font-awesome.css?1510652321"> -->
<link rel="stylesheet" type="text/css" href="/stylesheets/educoder/iconfont/iconfont.css"> <!--<link rel="stylesheet" type="text/css" href="http://47.96.87.25:48080/stylesheets/educoder/iconfont/iconfont.css">-->
<!--需要去build js配置-->
<link rel="stylesheet" type="text/css" href="/css/iconfont.css"> <link rel="stylesheet" type="text/css" href="/css/iconfont.css">
<style> <style>
/*<!--去除浏览器点击操作后有蓝色的底块-->*/ /*<!--去除浏览器点击操作后有蓝色的底块-->*/
@ -146,16 +148,30 @@
<!-- 在tpi js里加载这3个脚本 --> <!-- 在tpi js里加载这3个脚本 -->
<script> <script>
(function() { // Scoping function to avoid globals (function() { // Scoping function to avoid globals
var href = location.href; var href = location.href;
if (href.indexOf('/tasks/') != -1) { if(window.location.port === "3007"){
document.write('<script type="text/javascript" src="https://testeduplus2.educoder.net/assets/kindeditor/kindeditor.js"><\/script>'); if (href.indexOf('/tasks/') != -1) {
// build.js中会将这个url附加一个前缀 react/build document.write('<script type="text/javascript" src="https://newweb.educoder.net/assets/kindeditor/kindeditor.js"><\/script>');
document.write('<script type="text/javascript" src="/js/create_kindeditor.js"><\/script>'); // build.js中会将这个url附加一个前缀 react/build
document.write('<script type="text/javascript" src="https://testeduplus2.educoder.net/javascripts/educoder/edu_application.js"><\/script>'); document.write('<script type="text/javascript" src="/js/create_kindeditor.js"><\/script>');
} else if (href.indexOf('/paths/') != -1) { document.write('<script type="text/javascript" src="https://newweb.educoder.net/javascripts/educoder/edu_application.js"><\/script>');
document.write('<script type="text/javascript" src="https://testeduplus2.educoder.net/javascripts/educoder/edu_application.js"><\/script>'); } else if (href.indexOf('/paths/') != -1) {
document.write('<script type="text/javascript" src="https://newweb.educoder.net/javascripts/educoder/edu_application.js"><\/script>');
}
}
}else{
if (href.indexOf('/tasks/') != -1) {
document.write('<script type="text/javascript" src="/assets/kindeditor/kindeditor.js"><\/script>');
// build.js中会将这个url附加一个前缀 react/build
document.write('<script type="text/javascript" src="/js/create_kindeditor.js"><\/script>');
document.write('<script type="text/javascript" src="/javascripts/educoder/edu_application.js"><\/script>');
} else if (href.indexOf('/paths/') != -1) {
document.write('<script type="text/javascript" src="/javascripts/educoder/edu_application.js"><\/script>');
}
}
})(); })();
</script> </script>
<!-- <script type="text/javascript" src="https://testeduplus2.educoder.net/assets/kindeditor/kindeditor.js"></script> <!-- <script type="text/javascript" src="https://testeduplus2.educoder.net/assets/kindeditor/kindeditor.js"></script>

@ -38,7 +38,7 @@
<link rel="stylesheet" type="text/css" href="/css/css_min_all.css"> <link rel="stylesheet" type="text/css" href="/css/css_min_all.css">
<link href="https://testeduplus2.educoder.net/stylesheets/educoder/edu-all.css" rel="stylesheet" type="text/css"> <link href="/stylesheets/educoder/edu-all.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" type="text/css" href="//at.alicdn.com/t/font_653600_qa9lwwv74z.css"> <link rel="stylesheet" type="text/css" href="//at.alicdn.com/t/font_653600_qa9lwwv74z.css">

File diff suppressed because it is too large Load Diff

@ -0,0 +1,9 @@
/*!
* Cropper.js v1.5.2
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2019-06-30T06:01:02.389Z
*/.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}

File diff suppressed because one or more lines are too long

@ -1,103 +1,103 @@
function Base64() { function Base64() {
// private property // private property
_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// public method for encoding // public method for encoding
this.encode = function (input) { this.encode = function (input) {
var output = ""; var output = "";
var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
var i = 0; var i = 0;
input = _utf8_encode(input); input = _utf8_encode(input);
while (i < input.length) { while (i < input.length) {
chr1 = input.charCodeAt(i++); chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++);
enc1 = chr1 >> 2; enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63; enc4 = chr3 & 63;
if (isNaN(chr2)) { if (isNaN(chr2)) {
enc3 = enc4 = 64; enc3 = enc4 = 64;
} else if (isNaN(chr3)) { } else if (isNaN(chr3)) {
enc4 = 64; enc4 = 64;
} }
output = output + output = output +
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
_keyStr.charAt(enc3) + _keyStr.charAt(enc4); _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
} }
return output; return output;
} }
// public method for decoding // public method for decoding
this.decode = function (input) { this.decode = function (input) {
var output = ""; var output = "";
var chr1, chr2, chr3; var chr1, chr2, chr3;
var enc1, enc2, enc3, enc4; var enc1, enc2, enc3, enc4;
var i = 0; var i = 0;
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < input.length) { while (i < input.length) {
enc1 = _keyStr.indexOf(input.charAt(i++)); enc1 = _keyStr.indexOf(input.charAt(i++));
enc2 = _keyStr.indexOf(input.charAt(i++)); enc2 = _keyStr.indexOf(input.charAt(i++));
enc3 = _keyStr.indexOf(input.charAt(i++)); enc3 = _keyStr.indexOf(input.charAt(i++));
enc4 = _keyStr.indexOf(input.charAt(i++)); enc4 = _keyStr.indexOf(input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4); chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4; chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1); output = output + String.fromCharCode(chr1);
if (enc3 != 64) { if (enc3 != 64) {
output = output + String.fromCharCode(chr2); output = output + String.fromCharCode(chr2);
} }
if (enc4 != 64) { if (enc4 != 64) {
output = output + String.fromCharCode(chr3); output = output + String.fromCharCode(chr3);
} }
} }
output = _utf8_decode(output); output = _utf8_decode(output);
return output; return output;
} }
// private method for UTF-8 encoding // private method for UTF-8 encoding
_utf8_encode = function (string) { _utf8_encode = function (string) {
string = string.replace(/\r\n/g,"\n"); string = string.replace(/\r\n/g,"\n");
var utftext = ""; var utftext = "";
for (var n = 0; n < string.length; n++) { for (var n = 0; n < string.length; n++) {
var c = string.charCodeAt(n); var c = string.charCodeAt(n);
if (c < 128) { if (c < 128) {
utftext += String.fromCharCode(c); utftext += String.fromCharCode(c);
} else if((c > 127) && (c < 2048)) { } else if((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128); utftext += String.fromCharCode((c & 63) | 128);
} else { } else {
utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128); utftext += String.fromCharCode((c & 63) | 128);
} }
} }
return utftext; return utftext;
} }
// private method for UTF-8 decoding // private method for UTF-8 decoding
_utf8_decode = function (utftext) { _utf8_decode = function (utftext) {
var string = ""; var string = "";
var i = 0; var i = 0;
var c = c1 = c2 = 0; var c = c1 = c2 = 0;
while ( i < utftext.length ) { while ( i < utftext.length ) {
c = utftext.charCodeAt(i); c = utftext.charCodeAt(i);
if (c < 128) { if (c < 128) {
string += String.fromCharCode(c); string += String.fromCharCode(c);
i++; i++;
} else if((c > 191) && (c < 224)) { } else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1); c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2; i += 2;
} else { } else {
c2 = utftext.charCodeAt(i+1); c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2); c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3; i += 3;
} }
} }
return string; return string;
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,224 +1,225 @@
'use strict'; 'use strict';
// Do this as the first thing so that any code reading it knows the right env. // Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'production'; process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production'; process.env.NODE_ENV = 'production';
// Makes the script crash on unhandled rejections instead of silently // Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will // ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code. // terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', err => { process.on('unhandledRejection', err => {
throw err; throw err;
}); });
// Ensure environment variables are read. // Ensure environment variables are read.
require('../config/env'); require('../config/env');
const path = require('path'); const path = require('path');
const chalk = require('chalk'); const chalk = require('chalk');
const fs = require('fs-extra'); const fs = require('fs-extra');
const webpack = require('webpack'); const webpack = require('webpack');
const config = require('../config/webpack.config.prod'); const config = require('../config/webpack.config.prod');
const paths = require('../config/paths'); const paths = require('../config/paths');
const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles'); const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages'); const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
const printHostingInstructions = require('react-dev-utils/printHostingInstructions'); const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
const FileSizeReporter = require('react-dev-utils/FileSizeReporter'); const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
const printBuildError = require('react-dev-utils/printBuildError'); const printBuildError = require('react-dev-utils/printBuildError');
var CombinedStream = require('combined-stream'); var CombinedStream = require('combined-stream');
var fs2 = require('fs'); var fs2 = require('fs');
const measureFileSizesBeforeBuild = const measureFileSizesBeforeBuild =
FileSizeReporter.measureFileSizesBeforeBuild; FileSizeReporter.measureFileSizesBeforeBuild;
const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild;
const useYarn = fs.existsSync(paths.yarnLockFile); const useYarn = fs.existsSync(paths.yarnLockFile);
// These sizes are pretty large. We'll warn for bundles exceeding them. // These sizes are pretty large. We'll warn for bundles exceeding them.
const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024;
const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024; const WARN_AFTER_CHUNK_GZIP_SIZE = 1024 * 1024;
// Warn and crash if required files are missing // Warn and crash if required files are missing
if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
process.exit(1); process.exit(1);
} }
function removeExceptGitDir(dir) { function removeExceptGitDir(dir) {
// readdirSync // readdirSync
const list = fs2.readdirSync(dir) const list = fs2.readdirSync(dir)
// if (err) return done(err); // if (err) return done(err);
var pending = list.length; var pending = list.length;
// if (!pending) return done(null, results); // if (!pending) return done(null, results);
list.forEach(function(file) { list.forEach(function(file) {
if (file.indexOf('.git') == -1) { if (file.indexOf('.git') == -1) {
file = path.resolve(dir, file); file = path.resolve(dir, file);
fs.removeSync(file) fs.removeSync(file)
} }
}); });
} }
// First, read the current file sizes in build directory. // First, read the current file sizes in build directory.
// This lets us display how much they changed later. // This lets us display how much they changed later.
measureFileSizesBeforeBuild(paths.appBuild) measureFileSizesBeforeBuild(paths.appBuild)
.then(previousFileSizes => { .then(previousFileSizes => {
// Remove all content but keep the directory so that // Remove all content but keep the directory so that
// if you're in it, you don't end up in Trash // if you're in it, you don't end up in Trash
// fs.emptyDirSync(paths.appBuild); // fs.emptyDirSync(paths.appBuild);
console.log('removeExceptGitDir') console.log('removeExceptGitDir')
removeExceptGitDir(paths.appBuild) removeExceptGitDir(paths.appBuild)
console.log('copyPublicFolder') console.log('copyPublicFolder')
// Merge with the public folder // Merge with the public folder
copyPublicFolder(); copyPublicFolder();
// Start the webpack build // Start the webpack build
return build(previousFileSizes); return build(previousFileSizes);
}) })
.then( .then(
({ stats, previousFileSizes, warnings }) => { ({ stats, previousFileSizes, warnings }) => {
if (warnings.length) { if (warnings.length) {
console.log(chalk.yellow('Compiled with warnings.\n')); console.log(chalk.yellow('Compiled with warnings.\n'));
console.log(warnings.join('\n\n')); console.log(warnings.join('\n\n'));
console.log( console.log(
'\nSearch for the ' + '\nSearch for the ' +
chalk.underline(chalk.yellow('keywords')) + chalk.underline(chalk.yellow('keywords')) +
' to learn more about each warning.' ' to learn more about each warning.'
); );
console.log( console.log(
'To ignore, add ' + 'To ignore, add ' +
chalk.cyan('// eslint-disable-next-line') + chalk.cyan('// eslint-disable-next-line') +
' to the line before.\n' ' to the line before.\n'
); );
} else { } else {
console.log(chalk.green('Compiled successfully.\n')); console.log(chalk.green('Compiled successfully.\n'));
} }
console.log('File sizes after gzip:\n'); console.log('File sizes after gzip:\n');
printFileSizesAfterBuild( printFileSizesAfterBuild(
stats, stats,
previousFileSizes, previousFileSizes,
paths.appBuild, paths.appBuild,
WARN_AFTER_BUNDLE_GZIP_SIZE, WARN_AFTER_BUNDLE_GZIP_SIZE,
WARN_AFTER_CHUNK_GZIP_SIZE WARN_AFTER_CHUNK_GZIP_SIZE
); );
console.log(); console.log();
const appPackage = require(paths.appPackageJson); const appPackage = require(paths.appPackageJson);
const publicUrl = paths.publicUrl; const publicUrl = paths.publicUrl;
const publicPath = config.output.publicPath; const publicPath = config.output.publicPath;
const buildFolder = path.relative(process.cwd(), paths.appBuild); const buildFolder = path.relative(process.cwd(), paths.appBuild);
printHostingInstructions( printHostingInstructions(
appPackage, appPackage,
publicUrl, publicUrl,
publicPath, publicPath,
buildFolder, buildFolder,
useYarn useYarn
); );
}, },
err => { err => {
console.log(chalk.red('Failed to compile.\n')); console.log(chalk.red('Failed to compile.\n'));
printBuildError(err); printBuildError(err);
process.exit(1); process.exit(1);
} }
); );
// Create the production build and print the deployment instructions. // Create the production build and print the deployment instructions.
function build(previousFileSizes) { function build(previousFileSizes) {
console.log('Creating an optimized production build...'); console.log('Creating an optimized production build...');
let compiler = webpack(config); let compiler = webpack(config);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
compiler.run((err, stats) => { compiler.run((err, stats) => {
if (err) { if (err) {
return reject(err); return reject(err);
} }
const messages = formatWebpackMessages(stats.toJson({}, true)); const messages = formatWebpackMessages(stats.toJson({}, true));
if (messages.errors.length) { if (messages.errors.length) {
// Only keep the first error. Others are often indicative // Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise. // of the same problem, but confuse the reader with noise.
if (messages.errors.length > 1) { if (messages.errors.length > 1) {
messages.errors.length = 1; messages.errors.length = 1;
} }
return reject(new Error(messages.errors.join('\n\n'))); return reject(new Error(messages.errors.join('\n\n')));
} }
if ( if (
process.env.CI && process.env.CI &&
(typeof process.env.CI !== 'string' || (typeof process.env.CI !== 'string' ||
process.env.CI.toLowerCase() !== 'false') && process.env.CI.toLowerCase() !== 'false') &&
messages.warnings.length messages.warnings.length
) { ) {
console.log( console.log(
chalk.yellow( chalk.yellow(
'\nTreating warnings as errors because process.env.CI = true.\n' + '\nTreating warnings as errors because process.env.CI = true.\n' +
'Most CI servers set it automatically.\n' 'Most CI servers set it automatically.\n'
) )
); );
return reject(new Error(messages.warnings.join('\n\n'))); return reject(new Error(messages.warnings.join('\n\n')));
} }
generateNewIndexJsp(); generateNewIndexJsp();
return resolve({ return resolve({
stats, stats,
previousFileSizes, previousFileSizes,
warnings: messages.warnings, warnings: messages.warnings,
}); });
}); });
}); });
} }
function copyPublicFolder() { function copyPublicFolder() {
fs.copySync(paths.appPublic, paths.appBuild, { fs.copySync(paths.appPublic, paths.appBuild, {
dereference: true, dereference: true,
filter: file => file !== paths.appHtml, filter: file => file !== paths.appHtml,
}); });
} }
function generateNewIndexJsp() { function generateNewIndexJsp() {
// var combinedStream = CombinedStream.create(); // var combinedStream = CombinedStream.create();
var filePath = paths.appBuild + '/index.html'; var filePath = paths.appBuild + '/index.html';
// var htmlContent = fs2.createReadStream( filePath ) // var htmlContent = fs2.createReadStream( filePath )
// stream没有replace方法 // stream没有replace方法
// htmlContent = htmlContent.replace('/js/js_min_all.js', '/react/build/js/js_min_all.js') // htmlContent = htmlContent.replace('/js/js_min_all.js', '/react/build/js/js_min_all.js')
// htmlContent = htmlContent.replace('/css/css_min_all.css', '/react/build/css/css_min_all.css') // htmlContent = htmlContent.replace('/css/css_min_all.css', '/react/build/css/css_min_all.css')
// combinedStream.append(htmlContent); // combinedStream.append(htmlContent);
// combinedStream.pipe(fs2.createWriteStream( filePath )); // combinedStream.pipe(fs2.createWriteStream( filePath ));
var outputPath = paths.appBuild + '/../../../public/react/build/index.html' var outputPath = paths.appBuild + '/../../../public/react/build/index.html'
fs2.readFile(filePath, 'utf8', function (err,data) { fs2.readFile(filePath, 'utf8', function (err,data) {
if (err) { if (err) {
return console.log(err); return console.log(err);
} }
const newVersion = '1.1.1' const newVersion = '1.1.1'
let cdnHost = 'https://shixun.educoder.net' let cdnHost = 'https://shixun.educoder.net'
cdnHost = 'https://cdn-testeduplus2.educoder.net' cdnHost = 'http://cdn.educoder.net'
cdnHost = '' cdnHost = ''
var result = data.replace('/js/js_min_all.js', `${cdnHost}/react/build/js/js_min_all.js?v=${newVersion}`) var result = data.replace('/js/js_min_all.js', `${cdnHost}/react/build/js/js_min_all.js?v=${newVersion}`)
.replace('/js/js_min_all_2.js', `${cdnHost}/react/build/js/js_min_all_2.js?v=${newVersion}`) .replace('/js/js_min_all_2.js', `${cdnHost}/react/build/js/js_min_all_2.js?v=${newVersion}`)
.replace('/css/css_min_all.css', `${cdnHost}/react/build/css/css_min_all.css?v=${newVersion}`) .replace('/css/css_min_all.css', `${cdnHost}/react/build/css/css_min_all.css?v=${newVersion}`)
.replace('/js/create_kindeditor.js', `${cdnHost}/react/build/js/create_kindeditor.js?v=${newVersion}`) .replace('/css/iconfont.css', `${cdnHost}/react/build/css/iconfont.css?v=${newVersion}`)
.replace(/\/js\/create_kindeditor.js/g, `${cdnHost}/react/build/js/create_kindeditor.js?v=${newVersion}`)
// .replace('/react/build/./static/css/main', `${cdnHost}/react/build/./static/css/main`)
// .replace('/react/build/./static/js/main', `${cdnHost}/react/build/./static/js/main`) // .replace('/react/build/./static/css/main', `${cdnHost}/react/build/./static/css/main`)
// .replace('/react/build/./static/js/main', `${cdnHost}/react/build/./static/js/main`)
.replace(/https:\/\/testeduplus2.educoder.net/g, '');
// .replace(/http:\/\/testbdweb.educoder.net/g, ''); .replace(/https:\/\/testeduplus2.educoder.net/g, '');
// .replace(/http:\/\/testbdweb.educoder.net/g, '');
// .replace('/css/css_min_all.css', '/react/build/css/css_min_all.css');
// .replace('/css/css_min_all.css', '/react/build/css/css_min_all.css');
fs2.writeFile(outputPath, result, 'utf8', function (err) {
if (err) return console.log(err); fs2.writeFile(outputPath, result, 'utf8', function (err) {
commitAndPush(); if (err) return console.log(err);
}); commitAndPush();
}); });
} });
}
function commitAndPush() {
var exec = require('child_process').exec; function commitAndPush() {
function puts(error, stdout, stderr) { console.log(stdout) } var exec = require('child_process').exec;
var options = {cwd:"./build"}; function puts(error, stdout, stderr) { console.log(stdout) }
exec("git status && git commit -am 'b' && git push", options, puts); var options = {cwd:"./build"};
exec("git status && git commit -am 'b' && git push", options, puts);
} }

@ -35,6 +35,10 @@
.editormd .CodeMirror-linenumbers { .editormd .CodeMirror-linenumbers {
padding: 0; padding: 0;
} }
.editormd-html-preview hr, .editormd-preview-container hr {
/* 颜色加深 */
border-top: 1px solid #ccc;
}
/* 重置掉antd的一些样式 */ /* 重置掉antd的一些样式 */
html, body { html, body {

@ -14,6 +14,9 @@ import '@icedesign/base/dist/ICEDesignBase.css';
import '@icedesign/base/index.scss'; import '@icedesign/base/index.scss';
import LoginDialog from './modules/login/LoginDialog' import LoginDialog from './modules/login/LoginDialog'
import Notcompletedysl from './modules/user/Notcompletedysl'
import Trialapplicationysl from './modules/login/Trialapplicationysl'
import Trialapplicationreview from './modules/user/Trialapplicationreview'
import Trialapplication from './modules/login/Trialapplication' import Trialapplication from './modules/login/Trialapplication'
import NotFoundPage from './NotFoundPage' import NotFoundPage from './NotFoundPage'
@ -110,6 +113,11 @@ const CoursesIndex = Loadable({
loader: () => import('./modules/courses/Index'), loader: () => import('./modules/courses/Index'),
loading: Loading, loading: Loading,
}) })
const SearchPage = Loadable({
loader: () => import('./search/SearchPage'),
loading: Loading,
})
// 课堂讨论 // 课堂讨论
// const BoardIndex = Loadable({ // const BoardIndex = Loadable({
// loader: () => import('./modules/courses/boards/BoardIndex'), // loader: () => import('./modules/courses/boards/BoardIndex'),
@ -207,6 +215,12 @@ const UsersInfo = Loadable({
loading: Loading, loading: Loading,
}) })
// 兴趣页面
const Interestpage = Loadable({
loader: () => import('./modules/login/EducoderInteresse'),
loading: Loading,
})
class App extends Component { class App extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
@ -218,7 +232,12 @@ class App extends Component {
componentDidMount() { componentDidMount() {
// force an update if the URL changes // force an update if the URL changes
history.listen(() => this.forceUpdate()); history.listen(() => {
this.forceUpdate()
const $ = window.$
// https://www.trustie.net/issues/21919 可能会有问题
$("html").animate({ scrollTop: $('html').scrollTop() - 0 })
});
initAxiosInterceptors(this.props) initAxiosInterceptors(this.props)
@ -241,12 +260,16 @@ class App extends Component {
render() { render() {
// let {isRenders} = this.state; // let {isRenders} = this.state;
return ( return (
<LocaleProvider locale={zhCN}> <LocaleProvider locale={zhCN}>
<MuiThemeProvider theme={theme}> <MuiThemeProvider theme={theme}>
<LoginDialog></LoginDialog> <LoginDialog {...this.props} {...this.state}></LoginDialog>
<Notcompletedysl {...this.props} {...this.state}></Notcompletedysl>
<Trialapplicationysl {...this.props} {...this.state}></Trialapplicationysl>
<Trialapplicationreview {...this.props} {...this.state}></Trialapplicationreview>
{/*{*/} {/*{*/}
{/* isRender === true?*/} {/* isRender === true?*/}
{/* <LoginDialog></LoginDialog> : ""*/} {/* <LoginDialog></LoginDialog> : ""*/}
@ -254,7 +277,7 @@ class App extends Component {
{/*{*/} {/*{*/}
{/* isRenders === true?*/} {/* isRenders === true?*/}
<Trialapplication></Trialapplication> {/*<Trialapplication></Trialapplication>*/}
{/*:""*/} {/*:""*/}
{/*}*/} {/*}*/}
@ -262,7 +285,7 @@ class App extends Component {
<Switch> <Switch>
{/*<Route path="/login" component={LoginRegisterPage}/>*/} {/*<Route path="/login" component={LoginRegisterPage}/>*/}
{/*认证*/} {/*认证*/}
{/*<Route path="/account" component={AccountPage}/>*/} <Route path="/account" component={AccountPage}/>
{/*403*/} {/*403*/}
<Route path="/403" component={Shixunauthority}/> <Route path="/403" component={Shixunauthority}/>
@ -289,7 +312,10 @@ class App extends Component {
<Route <Route
path="/changepassword" component={EducoderLogin} path="/changepassword" component={EducoderLogin}
/> />
<Route
path="/interesse" component={Interestpage}
/>
<Route path="/shixuns/new" component={Newshixuns}> <Route path="/shixuns/new" component={Newshixuns}>
</Route> </Route>
@ -307,15 +333,22 @@ class App extends Component {
<Route path="/fork_list" component={TPMshixunfork_listIndexComponent}> <Route path="/fork_list" component={TPMshixunfork_listIndexComponent}>
</Route> */} </Route> */}
<Route path="/forums" component={ForumsIndexComponent}> {/*<Route path="/forums" component={ForumsIndexComponent}>*/}
</Route> {/*</Route>*/}
{/*实训课程(原实训路径)*/} {/*实训课程(原实训路径)*/}
<Route path="/paths" component={ShixunPaths}></Route> <Route path="/paths" component={ShixunPaths}></Route>
<Route path="/search"
render={
(props)=>(<SearchPage {...this.props} {...props} {...this.state}></SearchPage>)
}
></Route>
{/*课堂*/} {/*课堂*/}
<Route path="/courses" component={CoursesIndex} {...this.props}></Route> <Route path="/courses" component={CoursesIndex} {...this.props}></Route>
{/* 课堂讨论 */} {/* 课堂讨论 */}
{/* <Route path="/board" component = {BoardIndex} {...this.props}></Route> */} {/* <Route path="/board" component = {BoardIndex} {...this.props}></Route> */}
@ -323,7 +356,8 @@ class App extends Component {
</Route> */} </Route> */}
{/* <Route path="/myshixuns/:shixunId/stages/:stageId" component={Index}/> */} {/* <Route path="/myshixuns/:shixunId/stages/:stageId" component={Index}/> */}
{/* 兴趣页面*/}
{/*<Route path="/interest" component={Interestpage}/>*/}
<Route path="/comment" component={CommentComponent}/> <Route path="/comment" component={CommentComponent}/>
<Route path="/testMaterial" component={TestMaterialDesignComponent}/> <Route path="/testMaterial" component={TestMaterialDesignComponent}/>

@ -2,22 +2,23 @@ import React from "react";
import axios from 'axios'; import axios from 'axios';
import { requestProxy } from "./indexEduplus2RequestProxy"; import { requestProxy } from "./indexEduplus2RequestProxy";
import { broadcastChannelOnmessage } from 'educoder' import { broadcastChannelOnmessage ,SetAppModel} from 'educoder';
import { notification } from 'antd'; import { notification } from 'antd';
import './index.css'
broadcastChannelOnmessage('refreshPage', () => { broadcastChannelOnmessage('refreshPage', () => {
window.location.reload() window.location.reload()
}) })
function locationurl(list){ function locationurl(list){
if (window.location.port === "3007") { if (window.location.port === "3007") {
} else { } else {
window.location.replace(list) window.location.replace(list)
} }
} }
// TODO 开发期多个身份切换 // TODO 开发期多个身份切换
const debugType = window.location.search.indexOf('debug=t') != -1 ? 'teacher' : const debugType =window.location.search.indexOf('debug=t') != -1 ? 'teacher' :
window.location.search.indexOf('debug=s') != -1 ? 'student' : 'admin' window.location.search.indexOf('debug=s') != -1 ? 'student' : 'admin'
window._debugType = debugType; window._debugType = debugType;
export function initAxiosInterceptors(props) { export function initAxiosInterceptors(props) {
@ -29,7 +30,8 @@ export function initAxiosInterceptors(props) {
var proxy = "http://localhost:3000" var proxy = "http://localhost:3000"
// 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"
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求 // 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求
@ -100,6 +102,7 @@ export function initAxiosInterceptors(props) {
if(response===undefined){ if(response===undefined){
return return
} }
const config = response.config
if (response.data.status === -1) { if (response.data.status === -1) {
// console.error('error:', response.data.message) // console.error('error:', response.data.message)
// throw new Error() // throw new Error()
@ -114,6 +117,9 @@ export function initAxiosInterceptors(props) {
notification.open({ notification.open({
message:"提示", message:"提示",
description: response.data.message || '服务器异常,请联系管理员。', description: response.data.message || '服务器异常,请联系管理员。',
style: {
zIndex: 99999999
},
}); });
// notification['error']({ // notification['error']({
// message:"提示", // message:"提示",
@ -138,9 +144,16 @@ export function initAxiosInterceptors(props) {
locationurl('/500'); locationurl('/500');
} }
if (response.data.status === 402) { // if (response.data.status === 402) {
locationurl(response.data.url); // console.log(response.data.status);
} // console.log(response.data);
// // locationurl(402);
// }
if (response.data.status === 401) {
return config;
}
// if (response.data.status === 407) { // if (response.data.status === 407) {
// 在app js 中解决 Trialapplication // 在app js 中解决 Trialapplication
// // </Trialapplication> // // </Trialapplication>

@ -1,16 +1,27 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom"; import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Loading extends Component { import { Spin } from 'antd';
render() {
// Loading class Loading extends Component {
return ( render() {
<div className="App" style={{minHeight: '800px'}}> // Loading
return (
</div> <div className="App" style={{minHeight: '800px',width:"100%"}}>
); <style>
} {
} `
.margintop{
export default Loading; margin-top:20%;
}
`
}
</style>
<Spin size="large" className={"margintop"}/>
</div>
);
}
}
export default Loading;

@ -1,7 +1,19 @@
/**
EDU_ADMIN = 1 # 超级管理员
EDU_BUSINESS = 2 # 运营人员
EDU_SHIXUN_MANAGER = 3 # 实训管理员
EDU_SHIXUN_MEMBER = 4 # 实训成员
EDU_CERTIFICATION_TEACHER = 5 # 平台认证的老师
EDU_GAME_MANAGER = 6 # TPI的创建者
EDU_TEACHER = 7 # 平台老师,但是未认证
EDU_NORMAL = 8 # 普通用户
*/
export const EDU_ADMIN = 1 // 超级管理员 export const EDU_ADMIN = 1 // 超级管理员
export const EDU_SHIXUN_MANAGER = 2 // 实训管理员 export const EDU_BUSINESS = 2 // # 运营人员
export const EDU_SHIXUN_MEMBER = 3 // 实训成员 export const EDU_SHIXUN_MANAGER = 3 // 实训管理员
export const EDU_CERTIFICATION_TEACHER = 4 // 平台认证的老师 export const EDU_SHIXUN_MEMBER = 4 // 实训成员
export const EDU_GAME_MANAGER = 5 // TPI的创建者 export const EDU_CERTIFICATION_TEACHER = 5 // 平台认证的老师
export const EDU_TEACHER = 6 // 平台老师,但是未认证 export const EDU_GAME_MANAGER = 6 // TPI的创建者
export const EDU_NORMAL = 7 // 普通用户 export const EDU_TEACHER = 7 // 平台老师,但是未认证
export const EDU_NORMAL = 8 // 普通用户

@ -1,63 +1,81 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import Snackbar from 'material-ui/Snackbar'; import Snackbar from 'material-ui/Snackbar';
import Fade from 'material-ui/transitions/Fade'; import Fade from 'material-ui/transitions/Fade';
import { notification } from 'antd'
export function SnackbarHOC(options = {}) { export function SnackbarHOC(options = {}) {
return function wrap(WrappedComponent) { return function wrap(WrappedComponent) {
return class Wrapper extends Component { return class Wrapper extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.showSnackbar = this.showSnackbar.bind(this) this.showSnackbar = this.showSnackbar.bind(this)
this.state = { this.state = {
snackbarText: '', snackbarText: '',
snackbarOpen: false, snackbarOpen: false,
} }
} }
handleSnackbarClose() { handleSnackbarClose() {
this.setState({ this.setState({
snackbarOpen: false, snackbarOpen: false,
snackbarVertical: '', snackbarVertical: '',
snackbarHorizontal: '', snackbarHorizontal: '',
}) })
} }
// 全局的snackbar this.props.showSnackbar调用即可 // 全局的snackbar this.props.showSnackbar调用即可
showSnackbar(text, vertical, horizontal) { // showSnackbar(description, message = "提示",icon) {
this.setState({ // // this.setState({
snackbarOpen: true, // // snackbarOpen: true,
snackbarText: text, // // snackbarText: text,
snackbarVertical: vertical, // // snackbarVertical: vertical,
snackbarHorizontal: horizontal, // // snackbarHorizontal: horizontal,
}) // // })
} // const data = {
render() { // message,
const { snackbarOpen, snackbarText, snackbarHorizontal, snackbarVertical } = this.state; // description
// }
// if (icon) {
return ( // data.icon = icon;
<React.Fragment> // }
<Snackbar // notification.open(data);
className={"rootSnackbar"} // }
style={{zIndex:30000}}
open={this.state.snackbarOpen} showSnackbar(text, vertical, horizontal) {
autoHideDuration={3000} this.setState({
anchorOrigin={{ vertical: this.state.snackbarVertical || 'top' snackbarOpen: true,
, horizontal: this.state.snackbarHorizontal || 'center' }} snackbarText: text,
onClose={() => this.handleSnackbarClose()} snackbarVertical: vertical,
transition={Fade} snackbarHorizontal: horizontal,
SnackbarContentProps={{ })
'aria-describedby': 'message-id', }
}}
resumeHideDuration={2000} render() {
message={<span id="message-id">{this.state.snackbarText}</span>} const { snackbarOpen, snackbarText, snackbarHorizontal, snackbarVertical } = this.state;
/>
<WrappedComponent {...this.props} showSnackbar={ this.showSnackbar } >
return (
</WrappedComponent> <React.Fragment>
</React.Fragment> <Snackbar
) className={"rootSnackbar"}
} style={{zIndex:30000}}
} open={this.state.snackbarOpen}
} autoHideDuration={3000}
anchorOrigin={{ vertical: this.state.snackbarVertical || 'top'
, horizontal: this.state.snackbarHorizontal || 'center' }}
onClose={() => this.handleSnackbarClose()}
transition={Fade}
SnackbarContentProps={{
'aria-describedby': 'message-id',
}}
resumeHideDuration={2000}
message={<span id="message-id">{this.state.snackbarText}</span>}
/>
<WrappedComponent {...this.props} showSnackbar={ this.showSnackbar } >
</WrappedComponent>
</React.Fragment>
)
}
}
}
} }

@ -1,7 +1,14 @@
import { bytesToSize } from 'educoder'; import { bytesToSize } from 'educoder';
export function markdownToHTML(oldContent) { export function isImageExtension(fileName) {
return fileName ? !!(fileName.match(/.(jpg|jpeg|png|gif)$/i)) : false
}
export function markdownToHTML(oldContent, selector) {
window.$('#md_div').html('') window.$('#md_div').html('')
// markdown to html // markdown to html
try {
var markdwonParser = window.editormd.markdownToHTML("md_div", { var markdwonParser = window.editormd.markdownToHTML("md_div", {
markdown: oldContent, markdown: oldContent,
emoji: true, emoji: true,
@ -11,7 +18,15 @@ export function markdownToHTML(oldContent) {
flowChart: true, // 默认不解析 flowChart: true, // 默认不解析
sequenceDiagram: true // 默认不解析 sequenceDiagram: true // 默认不解析
}); });
} catch(e) {
console.error(e)
}
const content = window.$('#md_div').html() const content = window.$('#md_div').html()
if (selector) {
window.$(selector).html(content)
}
return content return content
} }

@ -1,6 +1,6 @@
export function bytesToSize(bytes) { export function bytesToSize(bytes) {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == 0) return '0 Byte'; if (bytes == 0) return '0 Byte';
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; return parseFloat(bytes / Math.pow(1024, i), 2).toFixed(1) + ' ' + sizes[i];
} }

@ -1,40 +1,62 @@
const isDev = window.location.port == 3007; const isDev = window.location.port == 3007;
export function getImageUrl(path) { export function getImageUrl(path) {
// https://www.educoder.net // https://www.educoder.net
// https://testbdweb.trustie.net // https://testbdweb.trustie.net
// const local = 'http://localhost:3000' // const local = 'http://localhost:3000'
const local = 'https://testeduplus2.educoder.net' const local = 'http://47.96.87.25:48080'
if (isDev) { if (isDev) {
return `${local}/${path}` return `${local}/${path}`
} }
return `/${path}`; return `/${path}`;
} }
export function getUrl(path, goTest) { export function setImagesUrl(path){
// https://www.educoder.net const local = 'http://47.96.87.25:48080'
// https://testbdweb.trustie.net let firstStr=path.substr(0,1);
console.log(firstStr);
// 如果想所有url定位到测试版可以反注释掉下面这行 if(firstStr=="/"){
//goTest = true return isDev?`${local}${path}`:`${path}`;
// testbdweb.educoder.net testbdweb.trustie.net }else{
// const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000' return isDev?`${local}/${path}`:`/${path}`;
const local = 'https://testeduplus2.educoder.net' }
if (isDev) { }
return `${local}${path?path:''}`
} export function getUrl(path, goTest) {
return `${path ? path: ''}`; // https://www.educoder.net
} // https://testbdweb.trustie.net
export function getUploadActionUrl(path, goTest) {
return `${getUrl()}/api/attachments.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}` // 如果想所有url定位到测试版可以反注释掉下面这行
} //goTest = true
// testbdweb.educoder.net testbdweb.trustie.net
export function test(path) { // const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000'
return `${path}`; // const local = 'https://testeduplus2.educoder.net'
} const local = 'http://47.96.87.25:48080'
if (isDev) {
export function toPath(path) { return `${local}${path?path:''}`
window.open(path, '_blank'); }
} return `${path ? path: ''}`;
}
export function getUrl2(path, goTest) {
const local = 'http://localhost:3000'
if (isDev) {
return `${local}${path?path:''}`
}
return `${path ? path: ''}`;
}
export function getUploadActionUrl(path, goTest) {
return `${getUrl()}/api/attachments.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
export function getUploadActionUrlOfAuth(id) {
return `${getUrl()}/api/users/accounts/${id}/auth_attachments.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
export function test(path) {
return `${path}`;
}
export function toPath(path) {
window.open(path, '_blank');
}
// export default queryString // export default queryString

@ -0,0 +1,288 @@
import React, { Component } from 'react';
import { getUrl2, isDev } from 'educoder'
const $ = window.$
let _url_origin = getUrl2()
// let _url_origin = `http://47.96.87.25:48080`;
function save_avatar(){
// if($(img_lg).html().trim() == ""){
// $("#avatar-name").html("请先选择图片上传").css("color", 'red');
// } else {
// $("#avatar-name").html("").css("color", '#333');
const previewId = this.props.previewId
var img_lg = document.getElementById(previewId || 'img-preview');
// 截图小的显示框内的内容
window.html2canvas(img_lg).then(function(canvas) {
// for test
// document.getElementById('canvasWrap').appendChild(canvas);
var dataUrl = canvas.toDataURL("image/jpeg");
console.log(dataUrl)
// TODO upload base64 image data to server
});
return
// 老版接口:
// html2canvas(img_lg, {
// allowTaint: true,
// taintTest: false,
// onrendered: function(canvas) {
// canvas.id = "mycanvas";
// //生成base64图片数据
// var dataUrl = canvas.toDataURL("image/jpeg");
// console.log(dataUrl)
// var newImg = document.getElementById("showImg");
// newImg.src = dataUrl;
// return;
// imagesAjax(dataUrl);
// $(".avatar-save").attr("disabled","true");
// }
// });
// }
}
/**
props 说明
imageId 源图片标签的id
previewId crop后预览dom的id
imageSrc 源图片src
width 数字格式
height 数字格式
*/
class Cropper extends Component {
state = {
};
handleChange = (info) => {
}
componentDidMount() {
this.options = {
aspectRatio: 1,
crop(event) {
// console.log(event.detail.x);
// console.log(event.detail.y);
// console.log(event.detail.width);
// console.log(event.detail.height);
// console.log(event.detail.rotate);
// console.log(event.detail.scaleX);
// console.log(event.detail.scaleY);
},
preview: this.props.previewId ? `#${this.props.previewId}` : '.img-preview',
}
if (!window.Cropper) {
$.ajaxSetup({
cache: true
});
const _isDev = isDev()
let _path = _isDev ? 'public' : 'build'
$('head').append($('<link rel="stylesheet" type="text/css" />')
.attr('href', `${_url_origin}/react/${_path}/js/cropper/cropper.min.css`));
$.getScript(
`${_url_origin}/react/${_path}/js/cropper/cropper.js`,
(data, textStatus, jqxhr) => {
});
$.getScript(
`${_url_origin}/react/${_path}/js/cropper/html2canvas.min.js`,
(data, textStatus, jqxhr) => {
});
}
setTimeout(() => {
const image = document.getElementById(this.props.imageId || '__image');
this.cropper = new window.Cropper(image, this.options);
}, 1000)
}
renew = (image) => {
this.cropper && this.cropper.destroy();
this.cropper = new window.Cropper(image, this.options);
}
render() {
const { width, height, previewId, imageSrc } = this.props;
return (
<div>
{/* This rule is very important, please do not ignore this! */}
<style>{`
.wrapper {
width: ${ width ? (width+'px') : '500px'};
height: ${ height ? (height+'px') : '500px'};
border: 1px solid #eee;
}
img {
max-width: 100%;
}
.preview-lg {
overflow: hidden;
background-color: #fff;
border-radius: 50%;
text-align: center;
}
`}</style>
<div className="wrapper">
{/* http://localhost:3007/images/footNavLogo.png 图片转了后不对
|| "/images/testPicture.jpg"
|| "/images/shixun0.jpg"
*/}
<img id={this.props.imageId || "__image"} src={`${imageSrc }`}></img>
</div>
{/* background: 'aquamarine',
'border-radius': '128px'
*/}
{!previewId && <div id="img-preview" className="img-preview preview-lg" style={{width: '128px', height: '128px', }}>
</div>}
{/* <img id="showImg" src="http://localhost:3007/images/testPicture.jpg"></img> */}
{/* <div id="canvasWrap"></div> */}
{/* <button onClick={save_avatar.bind(this)}>save </button> */}
</div>
);
}
}
export default Cropper;
// function aaa () {
// function showedit_headphoto() {
// var html = `
// <script src=\"../head/jquery.min.js\"><\/script>\n
// <link href=\"../head/cropper.min.css\" rel=\"stylesheet\">\n
// <link href=\"../head/sitelogo.css\" rel=\"stylesheet\">\n
// <script src=\"../head/bootstrap.min.js\"><\/script>\n
// <script src=\"../head/cropper.js\"><\/script>\n
// <script src=\"../head/sitelogo.js\"><\/script>\n
// <script src=\"../head/html2canvas.min.js\" type=\"text/javascript\" charset=\"utf-8\"><\/script>\n\n
// <div class=\"task-popup\" style=\"width: 550px;\">\n <div class=\"task-popup-title clearfix task-popup-bggrey\">上传头像<\/div>\n <div class=\"clearfix\">\n
// <div class=\"modal fade\" style=\"outline: none;\" id=\"avatar-modal\" aria-hidden=\"true\" aria-labelledby=\"avatar-modal-label\" role=\"dialog\" tabindex=\"-1\">\n
// <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <form class=\"avatar-form\">\n <div class=\"modal-body\">\n
// <div class=\"padding20\">\n <div class=\"avatar-upload\">\n <input class=\"avatar-src\" name=\"avatar_src\" type=\"hidden\">\n
// <input class=\"avatar-data\" name=\"avatar_data\" type=\"hidden\">\n\n <span id=\"avatar-name\"><\/span>\n
// <input class=\"avatar-input\" style=\"display:none;\" id=\"avatarInput\" value=\"avatars/User/116\" name=\"avatar_file\" type=\"file\">\n
// <input type=\"hidden\" id=\"source_id\" value=\"116\"/>\n <input type=\"hidden\" id=\"source_type\" value=\"User\"/>\n <\/div>\n
// <div class=\"row clearfix mt20 pl20\">\n <div class=\"task-form-45 fl panel-box-sizing uplaodImg\">\n
// <div class=\"avatar-wrapper\" id=\"wrapper_image_show\">\n <!--<span style=\"display: block;\">\n 选择你要上传的图片<br/>
// \n 仅支持JPG、GIF、PNG且文件小于2M\n <\/span>-->\n <\/div>\n
// <div class=\"row avatar-btns clearfix\">\n <a href=\"javascript:void(0);\" class=\"fl\" type=\"button\" onClick=\"$(\'input[id=avatarInput]\').click();\">重新上传<\/a>\n
// <!--<div class=\"btn-group\">\n <a href=\"javascript:void(0);\" class=\"fa fa-repeat fr mt5 color-grey-9\" data-method=\"rotate\" data-option=\"90\"
// type=\"button\" title=\"Rotate 90 degrees\">\n <\/a>\n <\/div>-->\n <\/div>\n <\/div>\n
// <div class=\"task-form-50 panel-box-sizing fr color-grey pr20\" style=\"width: 128px;\">\n <div class=\"edu-txt-center\">\n
// <div class=\"avatar-preview preview-lg radius\" id=\"imageHead\">\n
// <img alt=\"头像\" height=\"128\" nhname=\"avatar_image\" src=\"/images/avatars/User/116?1556802838\" width=\"128\" />\n <\/div>\n
// <span>头像预览<\/span>\n <\/div>\n
// <p class=\"color-grey-9 font-12 mt110 justify\">仅支持JPG、GIF、PNG且文件小于2M<\/p>\n <\/div>\n <\/div>\n
// <\/div>\n <div class=\"clearfix edu-txt-center mb20 mt10\">\n
// <a href=\"javascript:void(0);\" class=\"task-btn mr20\" onclick=\"hideModal()\">取消<\/a>\n
// <a href=\"javascript:void(0);\" class=\"avatar-save task-btn task-btn-orange\" data-dismiss=\"modal\">确定<\/a>\n
// <\/div>\n <\/div>\n <\/form>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n<\/div>\n\n<script>\n
// $(function () {\n new CropAvatar($(\'#crop-avatar\'), 1/1);\n\n //---------------------------头像上传-----------------------------//\n //做个下简易的验证 大小 格式\n $(\'#avatarInput\').on(\'change\', function(e) {\n var filemaxsize = 1024 * 5;//5M\n var target = $(e.target);\n var Size = target[0].files[0].size / 1024;\n if(Size > filemaxsize) {\n alert(\'图片过大,请重新选择!\');\n $(\".avatar-wrapper\").children().remove;\n return false;\n }\n if(!this.files[0].type.match(/image.*/)) {\n alert(\'请选择正确的图片!\')\n } else {\n /*var filename = document.querySelector(\"#avatar-name\");*/\n var texts = document.querySelector(\"#avatarInput\").value;\n var teststr = texts; //你这里的路径写错了\n testend = teststr.match(/[^\\\\]+\\.[^\\(]+/i); //直接完整文件名的\n /*filename.innerHTML = testend;\n $(filename).css(\"color\", \'#333\');*/\n $(\".avatar-save\").removeClass(\"task-btn-grey\").addClass(\"task-btn-orange\");\n $(\".avatar-save\").on(\"click\", save_avatar);\n }\n\n });\n });\n\n function save_avatar(){\n var img_lg = document.getElementById(\'imageHead\');\n if($(img_lg).html().trim() == \"\"){\n $(\"#avatar-name\").html(\"请先选择图片上传\").css(\"color\", \'red\');\n } else {\n $(\"#avatar-name\").html(\"\").css(\"color\", \'#333\');\n // 截图小的显示框内的内容\n html2canvas(img_lg, {\n allowTaint: true,\n taintTest: false,\n onrendered: function(canvas) {\n canvas.id = \"mycanvas\";\n //生成base64图片数据\n var dataUrl = canvas.toDataURL(\"image/jpeg\");\n var newImg = document.createElement(\"img\");\n newImg.src = dataUrl;\n imagesAjax(dataUrl);\n $(\".avatar-save\").attr(\"disabled\",\"true\");\n }\n });\n }\n }\n\n function imagesAjax(src) {\n var data = {};\n data.img = src;\n data.source_id = $(\'#source_id\').val();\n data.source_type = $(\'#source_type\').val();\n data.is_direct = 0;\n $.ajax({\n url: \"/upload_avatar\",\n beforeSend: function(xhr) {xhr.setRequestHeader(\'X-CSRF-Token\', $(\'meta[name=\"csrf-token\"]\').attr(\'content\'))},\n data: data,\n type: \"POST\",\n success: function (re) {\n console.log(re);\n console.log(1562050370);\n if(re){\n var o = JSON.parse(re);\n if (o.status !=0 ){\n console.log(o.message);\n } else {\n var imgSpan = $(\"img[nhname=\'avatar_image\']\");\n imgSpan.attr({\"src\": o.url + \'?1562050370\'});\n $(\"#user_code\").html(o.grade);\n notice_box_redirect(\"/users/shitou\", \"上传成功\");\n }\n } else {\n notice_box(\"上传出错\");\n }\n\n },\n error: function (e) {\n alert(e);\n }\n });\n }\n
// <\/script>`;
// pop_box_new(html, 550, 510);
// $("#imageHead img").attr({"src": $("#user_avatar_show").attr("src")});
// $("#wrapper_image_show img").attr({"src": $("#user_avatar_show").attr("src")});
// }
// $(function () {
// new CropAvatar($('#crop-avatar'), 1/1);
// //---------------------------头像上传-----------------------------//
// //做个下简易的验证 大小 格式
// $('#avatarInput').on('change', function(e) {
// var filemaxsize = 1024 * 5;//5M
// var target = $(e.target);
// var Size = target[0].files[0].size / 1024;
// if(Size > filemaxsize) {
// alert('图片过大,请重新选择!');
// $(".avatar-wrapper").children().remove;
// return false;
// }
// if(!this.files[0].type.match(/image.*/)) {
// alert('请选择正确的图片!')
// } else {
// /*var filename = document.querySelector("#avatar-name");*/
// var texts = document.querySelector("#avatarInput").value;
// var teststr = texts; //你这里的路径写错了
// testend = teststr.match(/[^\\\\]+\\.[^\\(]+/i); //直接完整文件名的
// /*filename.innerHTML = testend; $(filename).css("color", '#333');*/
// $(".avatar-save").removeClass("task-btn-grey").addClass("task-btn-orange");
// $(".avatar-save").on("click", save_avatar);
// }
// });
// });
// function save_avatar(){
// var img_lg = document.getElementById('imageHead');
// if($(img_lg).html().trim() == ""){
// $("#avatar-name").html("请先选择图片上传").css("color", 'red');
// } else {
// $("#avatar-name").html("").css("color", '#333');
// // 截图小的显示框内的内容
// html2canvas(img_lg, {
// allowTaint: true,
// taintTest: false,
// onrendered: function(canvas) {
// canvas.id = "mycanvas";
// //生成base64图片数据
// var dataUrl = canvas.toDataURL("image/jpeg");
// var newImg = document.createElement("img");
// newImg.src = dataUrl;
// imagesAjax(dataUrl);
// $(".avatar-save").attr("disabled","true");
// }
// });
// }
// }
// function imagesAjax(src) {
// var data = {};
// data.img = src;
// data.source_id = $('#source_id').val();
// data.source_type = $('#source_type').val();
// data.is_direct = 0;
// $.ajax({
// url: "/upload_avatar",
// beforeSend: function(xhr) {
// xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
// },
// data: data,
// type: "POST",
// success: function (re) {
// console.log(re);
// // console.log(1562050370);
// if(re){
// var o = JSON.parse(re);
// if (o.status !=0 ){
// console.log(o.message);
// } else {
// var imgSpan = $("img[nhname='avatar_image']");
// imgSpan.attr({"src": o.url + '?1562050370'});
// $("#user_code").html(o.grade);
// notice_box_redirect("/users/shitou", "上传成功");
// }
// } else {
// notice_box("上传出错");
// }
// },
// error: function (e) {
// alert(e);
// }
// });
// }
// }

@ -0,0 +1,114 @@
import React, { Component } from 'react';
const $ = window.jQuery
const jQuery = $;
if (!$.drag) {
(function($){
$.fn.dragValidator = function(options){
var x, drag = this, isMove = false, defaults = {
};
var options = $.extend(defaults, options);
//添加背景,文字,滑块
var html = '<div class="drag_bg"></div>'+
'<div class="drag_text" onselectstart="return false;" unselectable="on">拖动滑块验证</div>'+
'<div class="handler handler_bg"></div>';
this.append(html);
var handler = drag.find('.handler');
var drag_bg = drag.find('.drag_bg');
var text = drag.find('.drag_text');
var maxWidth = text.width() - handler.width(); //能滑动的最大间距
//鼠标按下时候的x轴的位置
handler.mousedown(function(e){
isMove = true;
x = e.pageX - parseInt(handler.css('left'), 10);
});
//鼠标指针在上下文移动时移动距离大于0小于最大间距滑块x轴位置等于鼠标移动距离
$(document).mousemove(function(e){
var _x = e.pageX - x;
var handler_offset = handler.offset();
var lastX = e.clientX -x;
lastX = Math.max(0,Math.min(maxWidth,lastX));
if(isMove){
if(_x > 0 && _x <= maxWidth){
handler.css({'left': lastX});
drag_bg.css({'width': lastX});
}
else if(lastX > maxWidth - 5 && lastX < maxWidth + 5 ){ //鼠标指针移动距离达到最大时清空事件
dragOk();
}
}
});
handler.mouseup(function(e){
isMove = false;
var _x = e.pageX - x;
if(text.text() != '验证通过' && _x < maxWidth){ //鼠标松开时,如果没有达到最大距离位置,滑块就返回初始位置
handler.animate({'left': 0});
drag_bg.animate({'width': 0});
}
});
//清空事件
function dragOk(){
options.dragOkCallback && options.dragOkCallback()
var kuaiwidth=drag.width() - handler.width() - 2;
handler.removeClass('handler_bg').addClass('handler_ok_bg');
handler.css({'left':kuaiwidth+'px'})
text.css({'width':kuaiwidth+'px'});
text.text('验证通过');
drag.css({'color': '#fff'});
drag_bg.css({'width':kuaiwidth+'px'})
handler.unbind('mousedown');
$(document).unbind('mousemove');
$(document).unbind('mouseup');
$("#user_verification_notice").html("");
$('#user_verification_notice').parent().hide();
}
};
})(jQuery);
}
class DragValidator extends Component {
componentDidMount () {
// if($("#reg-drag").length>0 && IsPC()){
$("#reg-drag").dragValidator({
height: this.props.height,
dragOkCallback: () => {
this.props.dragOkCallback && this.props.dragOkCallback()
}
});
// }else{
// $("#reg-drag").empty();
// }
}
empty() {
$("#reg-drag").empty();
}
render() {
const height = this.props.height || 45;
const className = this.props.className
const successGreenColor = this.props.successGreenColor || '#29bd8b'
// newMain clearfix
return (
<div id="reg-drag" className={`drag_slider ${className}`}>
<style>{`
.drag_slider .handler {
height: 100%;
}
.drag_slider {
height: ${height}px;
line-height: ${height}px;
}
.drag_slider .drag_bg {
height: ${height}px;
background-color: ${successGreenColor};
}
`}</style>
</div>
);
}
}
export default ( DragValidator );

@ -0,0 +1,48 @@
import React, { Component } from 'react';
import { Modal} from 'antd';
import axios from 'axios';
import '../../modules/user/common.css';
//完善个人资料
class Notcompleted extends Component {
constructor(props) {
super(props);
}
modalCancel=()=>{
window.location.href = "/";
}
setDownload=()=>{
window.location.href ='/account/profile';
}
render() {
console.log(this.props)
return(
<Modal
keyboard={false}
closable={false}
footer={null}
destroyOnClose={true}
title="提示"
centered={true}
visible={this.props.modalsType===undefined?false:this.props.modalsType}
width="530px"
>
<div className="educouddiv">
<div className={"tabeltext-alignleft mt10"}><p>您尚未完善个人资料</p></div>
<div className={"tabeltext-alignleft mt10"}><p>请在完成资料后提交试用申请</p></div>
<div className="clearfix mt30 edu-txt-center">
<a className="task-btn mr30" onClick={()=>this.modalCancel()}>取消</a>
<a className="task-btn task-btn-orange" onClick={()=>this.setDownload()}>立即完善资料</a>
</div>
</div>
</Modal>
)
}
}
export default Notcompleted;

@ -0,0 +1,51 @@
import React, { Component } from 'react';
import { Modal } from 'antd';
export function SetAppModel(options={}) {
return function wrap(WrappedComponent) {
return class Wrapper extends Component {
constructor(props) {
super(props);
this.state = {
}
}
modalCancel=()=>{
window.location.href = "/";
}
setDownload=()=>{
window.location.href ='/account/profile';
}
componentDidMount(){
console.log(this.props)
}
render() {
const { titlemessage, Modallisttype, Modallist, singleButton } = this.state;
return (
<Modal
keyboard={false}
closable={false}
footer={null}
destroyOnClose={true}
title="提示"
centered={true}
visible={true}
width="530px"
>
<div className="educouddiv">
<div className={"tabeltext-alignleft mt10"}><p>您尚未完善个人资料</p></div>
<div className={"tabeltext-alignleft mt10"}><p>请在完成资料后提交试用申请</p></div>
<div className="clearfix mt30 edu-txt-center">
<a className="task-btn mr30" onClick={()=>this.modalCancel()}>取消</a>
<a className="task-btn task-btn-orange" onClick={()=>this.setDownload()}>立即完善资料</a>
</div>
</div>
</Modal>
)
}
}
}
}

@ -1719,6 +1719,10 @@ const options = [{
label: '其他' label: '其他'
}], }],
}]; }];
function filter(inputValue, path) {
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
}
class City extends Component{ class City extends Component{
constructor(props){ constructor(props){
super(props); super(props);
@ -1739,11 +1743,16 @@ class City extends Component{
} }
} }
render(){ render(){
const { defaultValue } = this.props const { defaultValue, matchInputWidth, className, popupClassName } = this.props
const { value } = this.state const { value } = this.state
// 这里用请选择所在省市的话会触发chrome的地址选择
return( return(
<Cascader allowClear size="large" options={options} placeholder="请选择所在省市" onChange={this.onChange} <Cascader allowClear size="large" options={options} placeholder="请选择所在地" onChange={this.onChange}
matchInputWidth={matchInputWidth}
value={value} value={value}
showSearch={{ filter }}
className={className}
popupClassName={popupClassName}
></Cascader> ></Cascader>
) )
} }

@ -1,72 +1,83 @@
import React,{ Component } from "react"; import React,{ Component } from "react";
import TPMMDEditor from '../../../modules/tpm/challengesnew/TPMMDEditor' import TPMMDEditor from '../../../modules/tpm/challengesnew/TPMMDEditor'
import {markdownToHTML} from 'educoder' import {markdownToHTML} from 'educoder'
import './DMDEditor.css' import './DMDEditor.css'
// 需要父组件通过toShowMode、toMDMode 来控制一次只能打开一个DMDEditor // 需要父组件通过toShowMode、toMDMode 来控制一次只能打开一个DMDEditor
class DMDEditor extends Component{ class DMDEditor extends Component{
constructor(props){ constructor(props){
super(props); super(props);
this.mdRef = React.createRef() this.mdRef = React.createRef()
this.state={ this.state={
mdMode: false, mdMode: false,
// value: this.props.initValue // value: this.props.initValue
} }
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
} }
componentDidMount() { componentDidMount() {
// if(this.props.initValue != this.mdRef.current.getValue()) { // if(this.props.initValue != this.mdRef.current.getValue()) {
// this.mdRef.current.setValue(this.props.initValue) // this.mdRef.current.setValue(this.props.initValue)
// } // }
} }
toMDMode = () => { toMDMode = () => {
this.setState({mdMode: true}, () => { this.setState({mdMode: true}, () => {
this.mdRef.current.resize() this.mdRef.current.resize()
this.mdRef.current.setValue(this.props.initValue) this.mdRef.current.setValue(this.props.initValue)
}) })
this.props.toMDMode(this) this.props.toMDMode(this)
} }
toShowMode = () => { toShowMode = () => {
this.setState({mdMode: false}) this.setState({mdMode: false})
this.props.toShowMode && this.props.toShowMode(this) this.props.toShowMode && this.props.toShowMode(this)
} }
onCMBlur = () => { onCMBlur = () => {
this.toShowMode() this.toShowMode()
} }
onChange = (val) => { onChange = (val) => {
// this.setState({ value: val }) // this.setState({ value: val })
this.props.onChange(val) this.props.onChange(val)
} if (this.state.showError == true) {
render(){ this.setState({showError: false})
const { mdMode } = this.state; }
const { initValue } = this.props; }
showError = () => {
return( this.mdRef.current.showError()
<React.Fragment> this.setState({showError: true})
<style>{` }
render(){
`}</style> const { mdMode, showError } = this.state;
<div id="content_editorMd_show" className="new_li content_editorMd_show" const { initValue } = this.props;
style={{display: mdMode == true ? 'none' : '', color: initValue? '': '#999', alignItems: 'center', wordBreak: 'break-all'}} let _style = {}
dangerouslySetInnerHTML={{__html: initValue ? markdownToHTML(initValue):this.props.placeholder}} if (showError) {
onClick={this.toMDMode} _style.border = '1px solid red'
> }
_style = Object.assign(_style, {display: mdMode == true ? 'none' : '', color: initValue? '': '#999', alignItems: 'center', wordBreak: 'break-all'})
</div> return(
{/* <React.Fragment>
onCMBlur={this.onCMBlur} */} <style>{`
<TPMMDEditor
ref={this.mdRef} `}</style>
{...this.props} <div id="content_editorMd_show" className="new_li content_editorMd_show markdown-body"
initValue={initValue} style={_style}
className={`${this.props.className} ${mdMode == true ? '' : 'hideMd'}`} dangerouslySetInnerHTML={{__html: initValue ? markdownToHTML(initValue):this.props.placeholder}}
onChange={this.onChange} onClick={this.toMDMode}
></TPMMDEditor> >
</React.Fragment>
) </div>
} {/*
} onCMBlur={this.onCMBlur} */}
<TPMMDEditor
ref={this.mdRef}
{...this.props}
initValue={initValue}
className={`${this.props.className} ${mdMode == true ? '' : 'hideMd'}`}
onChange={this.onChange}
></TPMMDEditor>
</React.Fragment>
)
}
}
export default DMDEditor; export default DMDEditor;

@ -6,40 +6,23 @@ class MarkdownToHtml extends Component{
this.state={ this.state={
} }
} }
// componentDidUpdate = (prevProps) => { componentDidUpdate = (prevProps) => {
// if (this.props.content) { if (this.props.content) {
// if ( prevProps.content != this.props.content ) { if ( prevProps.content != this.props.content ) {
// if (!this.shixunDescr) { markdownToHTML(this.props.content, `.markdown_to_html_${this.props.selector || ''}`)
// this.shixunDescr = window.editormd.markdownToHTML("memo_content_editorMd", { }
// markdown: this.props.content, }
// htmlDecode: "style,script,iframe", // you can filter tags decode }
// taskList: true, componentDidMount () {
// tex: true, // 默认不解析 this.props.content && markdownToHTML(this.props.content, `.markdown_to_html_${this.props.selector || ''}`)
// flowChart: true, // 默认不解析
// sequenceDiagram: true // 默认不解析 }
// });
// } else {
// this.shixunDescr.html(this.props.content)
// }
// }
// }
// }
// componentDidMount () {
// if (!this.shixunDescr) {
// this.shixunDescr = window.editormd.markdownToHTML("memo_content_editorMd", {
// markdown: this.props.content,
// htmlDecode: "style,script,iframe", // you can filter tags decode
// taskList: true,
// tex: true, // 默认不解析
// flowChart: true, // 默认不解析
// sequenceDiagram: true // 默认不解析
// });
// }
// }
render(){ render(){
return( return(
<div id="memo_content_editorMd" className="new_li markdown-body" dangerouslySetInnerHTML={{__html: markdownToHTML(this.props.content)}}> <div id="memo_content_editorMd" className={`new_li markdown-body ${this.props.className} markdown_to_html_${this.props.selector || ''}`}
// dangerouslySetInnerHTML={{__html: markdownToHTML(this.props.content)}}
>
</div> </div>
) )
} }

@ -0,0 +1,17 @@
import React from 'react'
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
foreground_select: '#4CACFF'
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
export const ThemeContext = React.createContext(
themes.light // default value
);

@ -1,7 +1,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import {Link} from 'react-router-dom' import {Link} from 'react-router-dom'
const map={"blue":"blueFull","greyBack":"greyBack","grey":"greyWidthFixed","green":"greenBack"} const map={"blue":"blueFull","greyBack":"greyBack","grey":"greyWidthFixed","green":"greenBack",
'colorBlue': 'colorBlue', // 蓝字白底
}
class ActionBtn extends Component { class ActionBtn extends Component {
constructor(props) { constructor(props) {
super(props); super(props);

@ -1,49 +1,57 @@
import { from } from '_array-flatten@2.1.2@array-flatten'; import { from } from '_array-flatten@2.1.2@array-flatten';
// export { default as OrderStateUtil } from '../routes/Order/components/OrderStateUtil'; // export { default as OrderStateUtil } from '../routes/Order/components/OrderStateUtil';
export { getImageUrl as getImageUrl, getUrl as getUrl, getUploadActionUrl as getUploadActionUrl } from './UrlTool'; export { getImageUrl as getImageUrl, getUrl as getUrl, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
export { default as queryString } from './UrlTool2'; , getUploadActionUrl as getUploadActionUrl, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth } from './UrlTool';
export { default as queryString } from './UrlTool2';
export { SnackbarHOC as SnackbarHOC } from './SnackbarHOC';
export { SnackbarHOC as SnackbarHOC } from './SnackbarHOC';
export { trigger as trigger, on as on, off as off
, broadcastChannelPostMessage, broadcastChannelOnmessage } from './EventUtil'; export { trigger as trigger, on as on, off as off
, broadcastChannelPostMessage, broadcastChannelOnmessage } from './EventUtil';
export { updatePageParams as updatePageParams } from './RouterUtil';
export { updatePageParams as updatePageParams } from './RouterUtil';
export { bytesToSize as bytesToSize } from './UnitUtil';
export { bytesToSize as bytesToSize } from './UnitUtil';
export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll } from './TextUtil'
export { handleDateString, getNextHalfHourOfMoment,formatDuring } from './DateUtil' export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension } from './TextUtil'
export { handleDateString, getNextHalfHourOfMoment,formatDuring } from './DateUtil'
export { isDev as isDev } from './Env'
export { isDev as isDev } from './Env'
export { toStore as toStore, fromStore as fromStore } from './Store'
export { toStore as toStore, fromStore as fromStore } from './Store'
export { trace_collapse, trace, debug, info, warn, error, trace_c, debug_c, info_c, warn_c, error_c } from './LogUtil'
export { trace_collapse, trace, debug, info, warn, error, trace_c, debug_c, info_c, warn_c, error_c } from './LogUtil'
export { EDU_ADMIN, EDU_SHIXUN_MANAGER, EDU_SHIXUN_MEMBER, EDU_CERTIFICATION_TEACHER
, EDU_GAME_MANAGER, EDU_TEACHER, EDU_NORMAL} from './Const' export { EDU_ADMIN, EDU_BUSINESS, EDU_SHIXUN_MANAGER, EDU_SHIXUN_MEMBER, EDU_CERTIFICATION_TEACHER
, EDU_GAME_MANAGER, EDU_TEACHER, EDU_NORMAL} from './Const'
export { ModalHOC } from './components/ModalHOC'
export { default as ConditionToolTip } from './components/ConditionToolTip' export { themes, ThemeContext } from './context/ThemeContext'
export { default as DragValidator } from './components/DragValidator'
export { ModalHOC } from './components/ModalHOC'
export { default as PopInstruction } from './components/instruction/PopInstruction'
export { SetAppModel } from './components/SetAppModel'
export { default as City } from './components/form/City'
export { default as Cropper } from './components/Cropper'
export { default as ConditionToolTip } from './components/ConditionToolTip'
// course export { default as DragValidator } from './components/DragValidator'
export { default as WordsBtn } from './course/WordsBtn'
export { default as PopInstruction } from './components/instruction/PopInstruction'
export { default as ActionBtn } from './course/ActionBtn'
export { default as City } from './components/form/City'
export { default as MarkdownToHtml } from './components/markdown/MarkdownToHtml'
export { default as DMDEditor } from './components/markdown/DMDEditor' // course
export { default as WordsBtn } from './course/WordsBtn'
export { default as ActionBtn } from './course/ActionBtn'
export { default as MarkdownToHtml } from './components/markdown/MarkdownToHtml'
export { default as DMDEditor } from './components/markdown/DMDEditor'
export { default as ImageLayerHook } from './hooks/ImageLayerHook'

@ -0,0 +1,48 @@
import React, { useState, useEffect, memo } from 'react';
import ImageLayer from '../../modules/page/layers/ImageLayer';
import { isImageExtension } from 'educoder';
const $ = window.$;
function ImageLayerHook(props) {
const [showImage, setShowImage] = useState(false)
const [imageSrc, setImageSrc] = useState('')
const { parentSel, childSel, watchPropsArray } = props
const onImageLayerClose = () => {
setShowImage(false)
setImageSrc('')
}
const onDelegateClick = (event) => {
const imageSrc = event.target.src || event.target.getAttribute('src') || event.target.getAttribute('href')
// 判断imageSrc是否是图片
const fileName = event.target.innerHTML.trim()
if (isImageExtension(imageSrc.trim()) || isImageExtension(fileName) || event.target.tagName == 'IMG' || imageSrc.indexOf('base64,') != -1) {
// 非回复里的头像图片; 非emoticons
if (imageSrc.indexOf('/images/avatars/User') === -1 &&
imageSrc.indexOf('kindeditor/plugins/emoticons') === -1 ) {
setShowImage(true)
setImageSrc(imageSrc)
}
event.stopPropagation()
event.preventDefault && event.preventDefault()
event.originalEvent.preventDefault()
// event.originalEvent.stopPropagation()
// event.originalEvent.cancelBubble = true
return false;
}
}
useEffect(() => {
$(parentSel)
.delegate(childSel, "click", onDelegateClick);
return () => {
$(parentSel).undelegate(childSel, "click", onDelegateClick )
}
})
return (
<ImageLayer showImage={showImage} imageSrc={imageSrc} onImageLayerClose={onImageLayerClose}></ImageLayer>
)
}
export default memo(ImageLayerHook)

@ -17,7 +17,16 @@ class EvaluateSuccessEffectDisplay extends Component {
} }
componentDidMount() { componentDidMount() {
if (this.props.type == 'html') {
const iframe = document.getElementById('_displayIframe')
if (iframe && iframe.contentWindow) {
iframe.contentWindow.open()
iframe.contentWindow.document.write(this.props.iframe_src);
iframe.contentWindow.document.close();
} else {
console.error('not mounted')
}
}
} }
hidepicture = () => { hidepicture = () => {
window.$('#picture_display').hide(); window.$('#picture_display').hide();
@ -26,7 +35,7 @@ class EvaluateSuccessEffectDisplay extends Component {
// qrcode // qrcode
// const type = 'image' // 'qrcode' // const type = 'image' // 'qrcode'
const { type, qrcode_str, const { type, qrcode_str,
answer_picture, orignal_picture, user_picture } = this.props; answer_picture, orignal_picture, user_picture, contents } = this.props;
if (type == 'qrcode') { if (type == 'qrcode') {
// 单张图片比如安卓评测完显示qrcode // 单张图片比如安卓评测完显示qrcode
return ( return (
@ -51,16 +60,16 @@ class EvaluateSuccessEffectDisplay extends Component {
</div> </div>
<div className="clearfix" id="picture-content"> <div className="clearfix" id="picture-content">
<div className="fl with33 mr03precent pt10 mb50"> <div className="fl with33 mr03precent pt10 mb50">
<img alt="Icon" {orignal_picture[0] && <img alt="Icon"
src={orignal_picture[0].pic_url}/> src={ orignal_picture[0].pic_url}/>}
</div> </div>
<div className="fl with33 mr03precent pt10 mb50"> <div className="fl with33 mr03precent pt10 mb50">
<img alt="Icon" {user_picture[0] && <img alt="Icon"
src={user_picture[0].pic_url}/> src={ user_picture[0].pic_url }/>}
</div> </div>
<div className="fl with33 mr03precent pt10 mb50"> <div className="fl with33 mr03precent pt10 mb50">
<img alt="Icon" { answer_picture[0] && <img alt="Icon"
src={answer_picture[0].pic_url}/> src={ answer_picture[0].pic_url}/> }
</div> </div>
</div> </div>
</div> </div>
@ -69,12 +78,16 @@ class EvaluateSuccessEffectDisplay extends Component {
if (type == "txt") { if (type == "txt") {
return ( return (
<div className="task-popup-content clearfix"> <div className="task-popup-content clearfix">
<div className="with49" style={{margin: '0 auto'}}> <div className="with80" style={{margin: '0 auto'}}>
<p className="color-blue font-18 mb20 edu-txt-center">实际输出</p> <p className="color-blue font-18 mb20 edu-txt-center">实际输出</p>
<textarea className="output-txt" readonly="" defaultValue={content}></textarea> <textarea className="output-txt" readonly="" defaultValue={contents}></textarea>
</div> </div>
</div> </div>
) )
} else if (type == "html") {
return (
<iframe id="_displayIframe"></iframe>
)
} }
/* <div className="with49 fr"> /* <div className="with49 fr">
@ -95,12 +108,17 @@ class EvaluateSuccessEffectDisplay extends Component {
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
iframe#_displayIframe {
width: 100%;
height: 100%;
}
`} `}
</style> </style>
<div className="photo_display"> <div className="photo_display">
<div className="task-popup"> <div className="task-popup">
<div className="task-popup-title clearfix"> <div className="task-popup-title clearfix">
<h3 className="fl color-grey3">查看效果</h3> <h3 className="fl color-grey3 mt4">查看效果</h3>
<a href="javascript:void(0);" onClick={this.hidepicture} <a href="javascript:void(0);" onClick={this.hidepicture}
data-tip-left="关闭查看效果" className="pop_close fr"> data-tip-left="关闭查看效果" className="pop_close fr">
<i className="fa fa-times-circle font-18 link-color-grey mt5"></i> <i className="fa fa-times-circle font-18 link-color-grey mt5"></i>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@ -33,4 +33,8 @@ body {
} }
.anticon anticon-paper-clip{ .anticon anticon-paper-clip{
color: #29bd8b !important; color: #29bd8b !important;
}
.MuiModal-root-15{
z-index: 1000 !important;
} }

@ -1,46 +1,46 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './index.css'; import './index.css';
import './indexPlus.css'; import './indexPlus.css';
import App from './App'; import App from './App';
// 加之前main.js 18.1MB // 加之前main.js 18.1MB
// import { message } from 'antd'; // import { message } from 'antd';
import message from 'antd/lib/message'; import message from 'antd/lib/message';
import 'antd/lib/message/style/css'; import 'antd/lib/message/style/css';
import { AppContainer } from 'react-hot-loader'; import { AppContainer } from 'react-hot-loader';
import registerServiceWorker from './registerServiceWorker'; import registerServiceWorker from './registerServiceWorker';
import { configureUrlQuery } from 'react-url-query'; import { configureUrlQuery } from 'react-url-query';
import history from './history'; import history from './history';
// link the history used in our app to url-query so it can update the URL with it. // link the history used in our app to url-query so it can update the URL with it.
configureUrlQuery({ history }); configureUrlQuery({ history });
// ----------------------------------------------------------------------------------- 请求配置 // ----------------------------------------------------------------------------------- 请求配置
window.__useKindEditor = false; window.__useKindEditor = false;
const render = (Component) => { const render = (Component) => {
ReactDOM.render( ReactDOM.render(
<AppContainer> <AppContainer {...this.props} {...this.state}>
<Component /> <Component {...this.props} {...this.state}/>
</AppContainer>, </AppContainer>,
document.getElementById('root') document.getElementById('root')
); );
} }
// ReactDOM.render( // ReactDOM.render(
// , // ,
// document.getElementById('root')); // document.getElementById('root'));
// registerServiceWorker(); // registerServiceWorker();
render(App); render(App);
if (module.hot) { if (module.hot) {
module.hot.accept('./App', () => { render(App) }); module.hot.accept('./App', () => { render(App) });
} }

@ -1,639 +0,0 @@
import React, {Component} from 'react';
import {Link} from "react-router-dom";
import axios from 'axios';
import {getImageUrl, trigger} from 'educoder';
import { Tooltip, message,Popover} from 'antd';
import CoursesListType from '../coursesPublic/CoursesListType';
import Addcourses from '../coursesPublic/Addcourses';
import '../css/Courses.css';
import Modals from "../../modals/Modals";
import AddStudentModal from '../members/modal/AddStudentModal'
import AddTeacherModal from '../members/modal/AddTeacherModal'
// 点击按钮复制功能
// function jsCopy(){
// var e = document.getElementById("copy_invite_code");
// e.select();
// document.execCommand("Copy");
// codesuccess()
// }
// 点击按钮复制功能
function jsCopy() {
var e = document.getElementById("copy_invite_code");
e.select();
document.execCommand("Copy");
codesuccess()
}
function codesuccess() {
message.success('复制成功');
};
class CoursesBanner extends Component {
constructor(props) {
super(props)
this.state = {
show: false,
Addcoursestypes: false,
modalsType: false,
modalsTopval: "",
loadtype: false,
metype: 0,
modalsBottomval: "",
antIcon:false,
coursedata:undefined
}
}
componentDidMount() {
this.updatabanner()
}
updatabanner=()=>{
let courseId = this.props.match.params.coursesId;
let url = "/courses/" + courseId + "/top_banner.json"
axios.get(url).then((result) => {
let data = result.data;
this.setState({
coursedata: data
})
// console.log(data)
})
}
showeditmenu = () => {
this.setState({
show: true,
})
}
hideeditmenu = () => {
this.setState({
show: false
})
}
tojoinclass = (val) => {
if (val === 1) {
this.setState({
Addcoursestypes: true,
})
} else {
this.setState({
Addcoursestypes: false,
})
}
}
showActionPoll=(i,s,ss)=>{
this.setState({
modalsType: true,
modalsTopval: s,
loadtype: false,
metype: i,
modalsBottomval: ss,
})
}
ActionPoll = (i) => {
let {coursedata}=this.state;
var s = "";
var ss = "";
if (i === 1) {
s = "课堂删除后数据将无法恢复,是否确定删除?";
this.showActionPoll(i,s)
}
if (i === 2) {
s = "您确定要设置为私有?";
this.showActionPoll(i,s)
}
if (i === 3) {
s = "您确定要设置为公开?";
this.showActionPoll(i,s)
}
if(i===4){
if(coursedata.code_halt === true){
var url = `/courses/${this.props.match.params.coursesId}/set_invite_code_halt.json`
axios.post(url, {}).then((result) => {
try {
if (result.data.status === 0){
this.updatabanner()
}
} catch (e) {
}
})
}else{
s = "邀请码停用后,用户不能主动加入该课堂了";
ss = "您是否确认停用?";
this.showActionPoll(i,s,ss)
}
}
if (i ===5) {
s = "复制将在后台执行,作业、资源、试卷都将复制到新课堂平台";
ss = "将为你创建一个新的同名课堂,请问是否继续?";
this.showActionPoll(i,s,ss)
}
}
//取消
modalCancel = () => {
this.setState({
modalsType: false,
modalsTopval: "",
modalsBottomval:"",
loadtype: false,
antIcon:false
})
}
// 确定
ModalAction = () => {
let {coursedata}=this.state;
var push = this.props.history;
let id = this.props.match.params.coursesId;
//删除
if (this.state.metype === 1) {
console.log("删除");
this.modalCancel();
var url = `/courses/${id}.json`
axios
.delete(url, {})
.then(function (response) {
console.log(response.data.status)
if (response.data.status === 0) {
message.success("删除成功", 1)
push.push(`/courses`)
}
})
}
//设为私有的
if (this.state.metype === 2) {
this.modalCancel();
var state = this.state;
var url = `/courses/${id}/set_public_or_private.json`
axios.post(url, {}).then((result) => {
if (result.data.status === 0) {
message.success("设为私有的成功", 1);
state.coursedata.is_public = false;
this.setState({
coursedata: state.coursedata,
})
}
})
}
//设为公有的
if (this.state.metype === 3) {
this.modalCancel();
var state = this.state;
var url = `/courses/${id}/set_public_or_private.json`
axios.post(url, {}).then((result) => {
if (result.data.status === 0) {
message.success("设为公有的成功", 1);
state.coursedata.is_public = true;
this.setState({
coursedata: state.coursedata,
})
}
})
}
//停用邀请码
if (this.state.metype === 4) {
this.modalCancel();
var url = `/courses/${id}/set_invite_code_halt.json`
axios.post(url, {}).then((result) => {
try {
if (result.data.status === 0) {
message.success(coursedata.code_halt === true?"启用用邀请码成功":"停用邀请码成功", 1);
this.updatabanner()
}
} catch (e) {
}
})
}
if (this.state.metype ===5) {
this.setState({
antIcon: true,
})
var url = `/courses/${id}/duplicate_course.json`
axios.post(url).then((response) => {
// window.location.href = "/courses/" + response.data.new_course_id;
window.location.href = "/courses/" + response.data.new_course_id+"/students";
})
}
if(this.state.metype===6){
this.setState({
antIcon: true,
})
var url =`/courses/${id}/exit_course.json`;
axios.post(url).then((response) => {
if(response.data.status===0){
window.location.href = "/users/" + this.props.current_user.login;
}
})
}
}
addTeacher = (isTeacher) => {
this.setState({ isTeacher }, () => {
this.refs.addTeacherModal.setVisible(true)
})
}
addStudent = () => {
this.refs.addStudentModal.setVisible(true)
}
addTeacherSuccess = (params) => {
trigger('addTeacherSuccess', JSON.stringify(params))
this.updatabanner()
}
addStudentSuccess = (params) => {
trigger('addStudentSuccess', JSON.stringify(params))
this.updatabanner()
}
//退出课堂按钮
exitclass=()=>{
this.setState({
modalsType: true,
modalsTopval: "退出后您将不再是本课题的成员,作品将全部被删除,",
modalsBottomval:"确定要退出该课堂吗?",
metype:6
})
}
//切换身份
switchidentity=(sum)=>{
let newurl=this.props.match.url;
let id = this.props.match.params.coursesId;
if(sum===1){
let url =`/courses/${id}/switch_to_student.json`;
axios.post(url).then((response) => {
if(response.data.status===0){
// window.location.href = "/users/" + this.props.current_user.login;
// this.props.history.replace(newurl);
window.location.href=newurl
}
})
}
if(sum===2){
let url =`/courses/${id}/switch_to_teacher.json`;
axios.post(url).then((response) => {
if(response.data.status===0){
// window.location.href = "/users/" + this.props.current_user.login;
// this.props.history.replace(newurl);
window.location.href=newurl
}
})
}
if(sum===3){
let url =`/courses/${id}/switch_to_assistant.json`;
axios.post(url).then((response) => {
if(response.data.status===0){
// window.location.href = "/users/" + this.props.current_user.login;
// this.props.history.replace(newurl);
window.location.href=newurl
}
})
}
}
postsettings=()=>{
window.location.href = "/courses/" + this.props.match.params.coursesId + "/settings";
}
render() {
let { Addcoursestypes, coursedata, modalsType, modalsTopval, loadtype,modalsBottomval,antIcon} = this.state;
return (
coursedata === undefined || coursedata.status===401? <div id="course_info_1309" className="courseHead" style={{height: '206px'}}></div>:<div id="course_info_1309" className="courseHead" style={{height: '206px'}}>
{Addcoursestypes === true ? <Addcourses
Addcoursestype={Addcoursestypes}
hideAddcoursestype={() => this.tojoinclass(2)}
/> : ""}
<Modals
modalsType={modalsType}
modalsTopval={modalsTopval}
loadtype={loadtype}
modalsBottomval={modalsBottomval}
modalCancel={this.modalCancel}
modalSave={this.ModalAction}
antIcon={antIcon}
></Modals>
<AddTeacherModal ref="addTeacherModal"
{...this.props}
isTeacher={this.state.isTeacher}
moduleName={this.state.isTeacher ? "教师" : "助教"}
addTeacherSuccess={this.addTeacherSuccess}
></AddTeacherModal>
<AddStudentModal ref="addStudentModal"
{...this.props}
moduleName="学生"
addStudentSuccess={this.addStudentSuccess}
></AddStudentModal>
<div className="educontent clearfix educontentTop">
<div className="color-white clearfix mb10">
{
coursedata===undefined || coursedata.status===401 || coursedata.status===407?"":
<Tooltip placement="bottom" title={coursedata&&coursedata.name.length<38?"":coursedata.name}>
<span className="font-24 fl bannername">{coursedata.name}</span>
</Tooltip>
}
{/*访
公开公开课堂非课堂成员可以访问*/}
<span className={"TabsWarp"}>
<CoursesListType
typelist={coursedata.course_end === true ? ["已结束"] : coursedata.is_public === true ? ["公开"] : ["私有"]}
typesylename={"mt10"} tipval={coursedata.is_public === true?"":"私有课堂,非课堂成员不能访问"}/>
</span>
</div>
<div className="clearfix ">
<div className="fl fl mr40 mb20">
<a href={"/users/" + coursedata.teacher_login} className="fl">
<img alt="头像" className="radius fl mt3 bannerimgname"
src={getImageUrl(`images/` + coursedata.teacher_img)}/>
</a>
<div className="fl mt13">
<p className="color-white">
<a href={"/users/" + coursedata.teacher_login}
className="color-white bannnerusername">{coursedata.teacher_name}</a>
</p>
</div>
<div className="fl mt13">
<p className="color-white bannnerusernames">{coursedata.teacher_school}</p>
</div>
</div>
<div>
{coursedata.switch_to_student === true ?
<Tooltip placement="bottom" title={"切换至学生可进行提交作品、答题等操作"}>
<a className="fr user_default_btn user_blue_btn mr20 font-18"
onClick={()=>this.switchidentity(1)}
> 切换为学生 </a>
</Tooltip>
:""}
{coursedata.switch_to_teacher === true ?
<Tooltip placement="bottom" title={"由学生身份切换至教师"}>
<a className="fr user_default_btn user_blue_btn mr20 font-18"
onClick={()=>this.switchidentity(2)}
> 切换为老师 </a>
</Tooltip>:""}
{coursedata.switch_to_assistant === true ?
<Tooltip placement="bottom" title={"由学生身份切换至助教"}>
<a className="fr user_default_btn user_blue_btn mr20 font-18"
onClick={()=>this.switchidentity(3)}
> 切换为助教 </a>
</Tooltip>:""}
{coursedata.course_identity === 6&&coursedata.educoder_teacher===false?
<a className="fr user_default_btn task-btn-orange font-18 mr20" id="shixun_operation"
onClick={() => this.tojoinclass(1)}>加入课堂</a>: ""}
{coursedata.course_identity === 6&&coursedata.educoder_teacher===true?
<a className="fr user_default_btn task-btn-orange font-18 mr20" id="shixun_operation"
onClick={() => this.tojoinclass(1)}>加入课堂</a>: ""}
{coursedata.course_identity === 6&&coursedata.educoder_teacher===true?
<a className="fr user_default_btn user_blue_btn mr20 font-18" onClick={() => this.ActionPoll(5)}> 复制课堂 </a>: ""}
{this.props.isStudent()?<a className="fr user_default_btn user_blue_btn mr20 font-18"
onClick={() => this.exitclass()}
> 退出课堂 </a>:""}
</div>
<div className="clearfix clearfixborder">
<ul className="fl color-grey-eb pathInfo pathInfobox mt10">
<li className={"mt7"}>
<Link to={"/courses/"+this.props.match.params.coursesId+"/teachers"}>
<span className="color-grey-c fl font-16">教师</span>
<span
className="color-white fl font-16 bannerurli">{coursedata.teacher_count}</span>
</Link>
</li>
<li className={"mt7"}>
<Link to={"/courses/"+this.props.match.params.coursesId+"/students"}>
<span className="color-grey-c fl font-16">学生</span>
<span
className={coursedata.credit===null?"color-white fl font-16":"color-white fl font-16 bannerurli"}>{coursedata.student_count}</span>
</Link>
</li>
{/*<li className={"mt7"}>*/}
{/*<a>*/}
{/*<span className="color-grey-c fl font-16">分班</span>*/}
{/*<span className="color-white fl font-16 bannerurli">{coursedata.course_group_count}</span>*/}
{/*</a>*/}
{/*</li>*/}
{coursedata.credit===null?"":<li className={"mt7"}>
<a>
<span className="color-grey-c fl font-16 mr10">学分</span>
<span className="color-white fl font-16 "
>{coursedata.credit}</span>
</a>
</li>}
{/*{coursedata.course_end===true? <li className={"mt7"}>*/}
{/*<span className="color-grey-c fl font-16">已结束</span>*/}
{/*</li>:<li className={"mt7"}>*/}
{/*<span className="color-grey-c fl font-16"> 结束</span>*/}
{/*<span className="color-white fl font-16"*/}
{/*style={{*/}
{/*marginLeft:coursedata.deadline===null?"":'8px'*/}
{/*}}*/}
{/*>{coursedata.deadline===null?"--":coursedata.deadline}</span>*/}
{/*</li>}*/}
</ul>
{this.props.isAdmin()? <ul className="fr color-grey-eb pathInfo pathInfobox mt10"
style={{
position: "relative"
}}
>
<li className={"mt7 mr10im"}>
<a onClick={()=>this.addTeacher(true)}>
<span className="color-white fl font-16 bannerurli width100f">添加老师</span>
</a>
</li>
<li className={"mt7 mr10im"}>
<a onClick={()=>this.addTeacher(false)}>
<span className="color-white fl font-16 bannerurli width100f">添加助教</span>
</a>
</li>
<li className={"mt7 mr10im"}>
<a onClick={()=>this.addStudent()}>
<span className="color-white fl font-16 bannerurli width100f">添加学生</span>
</a>
</li>
<li className={"mt7 mr10im ml10"} style={{overflow:"hidden"}}>
<a>
<span className="color-grey-c fl font-16">邀请码</span>
<span
className={coursedata.code_halt === true? "color-white fl font-16 bannerurli width75f" : "color-white fl font-16 bannerurli width107f marleftf10 color-orange-tip"}>
{coursedata.code_halt === true? "已停用" : coursedata.invite_code}
{coursedata.code_halt === true ? "" :
<Tooltip placement="bottom" title={
<pre>
成员可以通过邀请码主动加入课堂<br/>
点击立刻复制邀请码
</pre>
}>
<i className="iconfont icon-fuzhi color-white font-14 ml10"
onClick={() => {
jsCopy()
}}></i>
</Tooltip>
}
<input id="copy_invite_code" value={coursedata.invite_code}/>
</span>
</a>
</li>
<li className={"mt7 ml10 mr0 "}>
<style>
{
`
.defaults{cursor:default}
`
}
</style>
<Popover placement="bottom" content={
<ul className="sandianbox" style={{
display: 'block',
right: "-113px",
top: "20px"
}}>
{coursedata.is_public === true?coursedata.course_identity <3?
<div className={"defaults"} onClick={() => this.ActionPoll(2)}>设为私有</div>: "" : ""}
{coursedata.is_public === false?coursedata.course_identity <3?
<div className={"defaults"} onClick={() => this.ActionPoll(3)}>设为公开</div> : "": ""}
{coursedata.course_identity <3? <div className={"defaults"} onClick={() => this.ActionPoll(4)}>
{coursedata.code_halt === true ?"启用邀请码":"停用邀请码"}
</div>:""}
<div className={"defaults"} onClick={this.postsettings}>设置</div>
<div className={"defaults"} onClick={() => this.ActionPoll(5)}>复制
</div>
{coursedata.is_admin===true?coursedata.course_identity <3?<div className={"defaults"} onClick={() => this.ActionPoll(1)}>删除</div>
:"":""}
</ul>
} trigger="hover">
<i className="iconfont icon-weibiaoti12 color-white font-14 relative"></i>
</Popover>
</li>
</ul>:""}
</div>
</div>
</div>
</div>
)
}
}
export default CoursesBanner;
// let id=this.props.match.params.coursesId;
//
// let url="/courses/"+id+"/top_banner.json"
// axios.get(url).then((result)=>{
// if(result.status===200){
// let data=result.data;
// this.setState({
// coursebannerlist:result.data,
// name: result.data.name,
// teacher_name: result.data.teacher_name,
// teacher_login: result.data.teacher_login,
// teacher_img: result.data.teacher_img,
// teacher_school: result.data.teacher_school,
// teacher_count: result.data.teacher_count,
// student_count: result.data.student_count,
// course_group_count: result.data.course_group_count,
// credit: result.data.credit,
// course_end: result.data.course_end,
// deadline: result.data.deadline,
// is_teacher: result.data.is_teacher,
// is_student: result.data.is_student,
// is_admin: result.data.is_admin,
// is_public: result.data.is_public,
// code_halt: result.data.code_halt,
// invite_code: result.data.invite_code,
// switch_to_student: result.data.switch_to_student,
// switch_to_teacher: result.data.switch_to_teacher,
// join_course: result.data.join_course,
// copy_course: result.data.copy_course,
// })
// }
// coursebannerlist:undefined,
// name: undefined,
// teacher_name: undefined,
// teacher_login: undefined,
// teacher_img: undefined,
// teacher_school: undefined,
// teacher_count: undefined,
// student_count: undefined,
// course_group_count: undefined,
// credit: undefined,
// course_end: undefined,
// deadline: undefined,
// is_teacher: undefined,
// is_student: undefined,
// is_admin: undefined,
// is_public: undefined,
// code_halt: undefined,
// invite_code:undefined,
// switch_to_student: undefined,
// switch_to_teacher: undefined,
// join_course: undefined,
// copy_course: undefined,
// }).catch((error)=>{
// console.log(error);
// })

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

Loading…
Cancel
Save