Merge branch 'dev_aliyun' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
@ -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: 已完成
|
@ -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">您可以稍后尝试
|
||||||
|
您可以稍后尝试 <a href="javascript:history.back()" class="color-blue">返回上页</a>,或者
|
||||||
|
<a target="_blank" href="//shang.qq.com/wpa/qunwpa?idkey=2f2043d88c1bd61d182b98bf1e061c6185e23055bec832c07d8148fe11c5a6cd" class="color-blue">QQ反馈>></a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 9.6 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 27 KiB |
@ -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',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 154 KiB |
@ -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}
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
@ -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,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];
|
||||||
}
|
}
|
@ -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,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)
|
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 29 KiB |
@ -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) });
|
||||||
}
|
}
|
||||||
|