diff --git a/.gitignore b/.gitignore
index 09b9eab36..98965d6da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@
/.idea/*
# Ignore react node_modules
+/public/react/.cache
/public/react/build
/public/react/build/
/public/react/node_modules/
@@ -60,5 +61,4 @@ vendor/bundle/
/public/images/avatars
/public/files
/workspace
-/log
-
+/log
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 4cabd09b9..44f4937ad 100644
--- a/Gemfile
+++ b/Gemfile
@@ -91,3 +91,5 @@ gem 'bulk_insert'
# elasticsearch
gem 'searchkick'
+
+gem 'aasm'
diff --git a/Gemfile.lock b/Gemfile.lock
index 94893687b..60e1ae811 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -8,6 +8,8 @@ PATH
GEM
remote: https://gems.ruby-china.com/
specs:
+ aasm (5.0.5)
+ concurrent-ruby (~> 1.0)
actioncable (5.2.1)
actionpack (= 5.2.1)
nio4r (~> 2.0)
@@ -324,6 +326,7 @@ PLATFORMS
ruby
DEPENDENCIES
+ aasm
active_decorator
acts-as-taggable-on (~> 6.0)
awesome_print
diff --git a/app/controllers/bidding_users_controller.rb b/app/controllers/bidding_users_controller.rb
new file mode 100644
index 000000000..ad0de6587
--- /dev/null
+++ b/app/controllers/bidding_users_controller.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/project_package_categories_controller.rb b/app/controllers/project_package_categories_controller.rb
new file mode 100644
index 000000000..80d364a05
--- /dev/null
+++ b/app/controllers/project_package_categories_controller.rb
@@ -0,0 +1,6 @@
+class ProjectPackageCategoriesController < ApplicationController
+ def index
+ categories = ProjectPackageCategory.cached_data
+ render_ok(count: categories.size, categories: categories)
+ end
+end
\ No newline at end of file
diff --git a/app/controllers/project_packages_controller.rb b/app/controllers/project_packages_controller.rb
new file mode 100644
index 000000000..de896c920
--- /dev/null
+++ b/app/controllers/project_packages_controller.rb
@@ -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
\ No newline at end of file
diff --git a/app/controllers/subjects_controller.rb b/app/controllers/subjects_controller.rb
index 37206a52f..b372eb2c0 100644
--- a/app/controllers/subjects_controller.rb
+++ b/app/controllers/subjects_controller.rb
@@ -194,7 +194,7 @@ class SubjectsController < ApplicationController
stages.each do |stage|
category = CourseSecondCategory.where(name: stage.name, course_id: @course.id, category_type: "shixun_homework").first ||
CourseSecondCategory.create!(name: stage.name, course_id: @course.id, category_type: "shixun_homework",
- course_module_id: course_module, position: course_module.course_second_categories.count + 1)
+ course_module_id: course_module.id, position: course_module.course_second_categories.count + 1)
stage.shixuns.where(id: params[:shixun_ids], status: 2).each do |shixun|
homework = HomeworksService.new.create_homework shixun, @course, category, current_user
diff --git a/app/decorators/project_package_decorator.rb b/app/decorators/project_package_decorator.rb
new file mode 100644
index 000000000..7dec24565
--- /dev/null
+++ b/app/decorators/project_package_decorator.rb
@@ -0,0 +1,5 @@
+module ProjectPackageDecorator
+ extend ApplicationDecorator
+
+ display_time_method :updated_at, :deadline_at, :published_at
+end
\ No newline at end of file
diff --git a/app/forms/project_packages/save_form.rb b/app/forms/project_packages/save_form.rb
new file mode 100644
index 000000000..5cc801d2d
--- /dev/null
+++ b/app/forms/project_packages/save_form.rb
@@ -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
diff --git a/app/models/bidding_user.rb b/app/models/bidding_user.rb
new file mode 100644
index 000000000..b518fb45e
--- /dev/null
+++ b/app/models/bidding_user.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/project_package.rb b/app/models/project_package.rb
new file mode 100644
index 000000000..fc541097a
--- /dev/null
+++ b/app/models/project_package.rb
@@ -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
diff --git a/app/models/project_package_apply.rb b/app/models/project_package_apply.rb
new file mode 100644
index 000000000..5116f075f
--- /dev/null
+++ b/app/models/project_package_apply.rb
@@ -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
\ No newline at end of file
diff --git a/app/models/project_package_category.rb b/app/models/project_package_category.rb
new file mode 100644
index 000000000..403fdf4cb
--- /dev/null
+++ b/app/models/project_package_category.rb
@@ -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
\ No newline at end of file
diff --git a/app/services/project_packages/agree_apply_service.rb b/app/services/project_packages/agree_apply_service.rb
new file mode 100644
index 000000000..7cef7f70c
--- /dev/null
+++ b/app/services/project_packages/agree_apply_service.rb
@@ -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
\ No newline at end of file
diff --git a/app/services/project_packages/apply_publish_service.rb b/app/services/project_packages/apply_publish_service.rb
new file mode 100644
index 000000000..157ed2cb6
--- /dev/null
+++ b/app/services/project_packages/apply_publish_service.rb
@@ -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
diff --git a/app/services/project_packages/bidding_service.rb b/app/services/project_packages/bidding_service.rb
new file mode 100644
index 000000000..125f707e7
--- /dev/null
+++ b/app/services/project_packages/bidding_service.rb
@@ -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
\ No newline at end of file
diff --git a/app/services/project_packages/end_bidding_service.rb b/app/services/project_packages/end_bidding_service.rb
new file mode 100644
index 000000000..6d43db949
--- /dev/null
+++ b/app/services/project_packages/end_bidding_service.rb
@@ -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
\ No newline at end of file
diff --git a/app/services/project_packages/refuse_apply_service.rb b/app/services/project_packages/refuse_apply_service.rb
new file mode 100644
index 000000000..142efe1e0
--- /dev/null
+++ b/app/services/project_packages/refuse_apply_service.rb
@@ -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
diff --git a/app/services/project_packages/save_service.rb b/app/services/project_packages/save_service.rb
new file mode 100644
index 000000000..bcfc19a10
--- /dev/null
+++ b/app/services/project_packages/save_service.rb
@@ -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
\ No newline at end of file
diff --git a/app/services/project_packages/win_bidding_service.rb b/app/services/project_packages/win_bidding_service.rb
new file mode 100644
index 000000000..831c29449
--- /dev/null
+++ b/app/services/project_packages/win_bidding_service.rb
@@ -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
\ No newline at end of file
diff --git a/app/tasks/check_project_package_deadline_task.rb b/app/tasks/check_project_package_deadline_task.rb
new file mode 100644
index 000000000..4ccec257e
--- /dev/null
+++ b/app/tasks/check_project_package_deadline_task.rb
@@ -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
diff --git a/app/views/project_packages/index.json.jbuilder b/app/views/project_packages/index.json.jbuilder
new file mode 100644
index 000000000..e26efe36e
--- /dev/null
+++ b/app/views/project_packages/index.json.jbuilder
@@ -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
\ No newline at end of file
diff --git a/app/views/project_packages/show.json.jbuilder b/app/views/project_packages/show.json.jbuilder
new file mode 100644
index 000000000..7fee5af29
--- /dev/null
+++ b/app/views/project_packages/show.json.jbuilder
@@ -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
\ No newline at end of file
diff --git a/config/locales/bidding_users/zh-CN.yml b/config/locales/bidding_users/zh-CN.yml
new file mode 100644
index 000000000..6420725fd
--- /dev/null
+++ b/config/locales/bidding_users/zh-CN.yml
@@ -0,0 +1,6 @@
+'zh-CN':
+ bidding_user:
+ status:
+ pending: 竞标中
+ bidding_won: 已中标
+ bidding_lost: 未中标
\ No newline at end of file
diff --git a/config/locales/forms/save_project_package_form.zh-CN.yml b/config/locales/forms/save_project_package_form.zh-CN.yml
new file mode 100644
index 000000000..662e59941
--- /dev/null
+++ b/config/locales/forms/save_project_package_form.zh-CN.yml
@@ -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: 验证码
diff --git a/config/locales/project_packages/zh-CN.yml b/config/locales/project_packages/zh-CN.yml
new file mode 100644
index 000000000..82075ffab
--- /dev/null
+++ b/config/locales/project_packages/zh-CN.yml
@@ -0,0 +1,9 @@
+zh-CN:
+ project_package:
+ status:
+ pending: 已创建
+ applying: 审核中
+ refused: 已拒绝
+ published: 竞标中
+ bidding_ended: 待选标
+ bidding_finished: 已完成
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index d69d432b3..9da4d4a7e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -683,6 +683,13 @@ Rails.application.routes.draw do
resources :students, only: [:index]
end
end
+
+ resources :project_package_categories, only: [:index]
+ resources :project_packages, only: [:index, :show, :create, :update, :destroy] do
+ resources :bidding_users, only: [:create] do
+ post :win, on: :collection
+ end
+ end
end
#git 认证回调
diff --git a/public/images/course/guide/1-1.png b/public/images/course/guide/1-1.png
new file mode 100644
index 000000000..c59467c5d
Binary files /dev/null and b/public/images/course/guide/1-1.png differ
diff --git a/public/images/course/guide/1-2.png b/public/images/course/guide/1-2.png
new file mode 100644
index 000000000..1a4f26f38
Binary files /dev/null and b/public/images/course/guide/1-2.png differ
diff --git a/public/images/course/guide/1-3.png b/public/images/course/guide/1-3.png
new file mode 100644
index 000000000..f54250747
Binary files /dev/null and b/public/images/course/guide/1-3.png differ
diff --git a/public/images/course/guide/1-4.png b/public/images/course/guide/1-4.png
new file mode 100644
index 000000000..6ecd3bfb7
Binary files /dev/null and b/public/images/course/guide/1-4.png differ
diff --git a/public/images/course/guide/1-5.png b/public/images/course/guide/1-5.png
new file mode 100644
index 000000000..c4205496d
Binary files /dev/null and b/public/images/course/guide/1-5.png differ
diff --git a/public/images/course/guide/1-6.png b/public/images/course/guide/1-6.png
new file mode 100644
index 000000000..5d5f76f7f
Binary files /dev/null and b/public/images/course/guide/1-6.png differ
diff --git a/public/images/course/guide/1-7.png b/public/images/course/guide/1-7.png
new file mode 100644
index 000000000..65cadf0c7
Binary files /dev/null and b/public/images/course/guide/1-7.png differ
diff --git a/public/images/educoder/EWM.jpg b/public/images/educoder/EWM.jpg
new file mode 100644
index 000000000..3b79aca8b
Binary files /dev/null and b/public/images/educoder/EWM.jpg differ
diff --git a/public/javascripts/educoder/edu_application.js b/public/javascripts/educoder/edu_application.js
index 91e04ce80..612a5a44b 100644
--- a/public/javascripts/educoder/edu_application.js
+++ b/public/javascripts/educoder/edu_application.js
@@ -1,1288 +1,1288 @@
-document.write("");
-
-/*!
- * JavaScript Cookie v2.2.0
- * https://github.com/js-cookie/js-cookie
- *
- * Copyright 2006, 2015 Klaus Hartl & Fagner Brack
- * Released under the MIT license
- */
-!function(e) {
- var n;
- if ("function" == typeof define && define.amd && (define(e),
- n = !0),
- "object" == typeof exports && (module.exports = e(),
- n = !0),
- !n) {
- var t = window.Cookies
- , o = window.Cookies = e();
- o.noConflict = function() {
- return window.Cookies = t,
- o
- }
- }
-}(function() {
- function e() {
- for (var e = 0, n = {}; e < arguments.length; e++) {
- var t = arguments[e];
- for (var o in t)
- n[o] = t[o]
- }
- return n
- }
- function n(e) {
- return e.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent)
- }
- return function t(o) {
- function r() {}
- function i(n, t, i) {
- if ("undefined" != typeof document) {
- "number" == typeof (i = e({
- path: "/"
- }, r.defaults, i)).expires && (i.expires = new Date(1 * new Date + 864e5 * i.expires)),
- i.expires = i.expires ? i.expires.toUTCString() : "";
- try {
- var c = JSON.stringify(t);
- /^[\{\[]/.test(c) && (t = c)
- } catch (e) {}
- t = o.write ? o.write(t, n) : encodeURIComponent(String(t)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent),
- n = encodeURIComponent(String(n)).replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent).replace(/[\(\)]/g, escape);
- var f = "";
- for (var u in i)
- i[u] && (f += "; " + u,
- !0 !== i[u] && (f += "=" + i[u].split(";")[0]));
- return document.cookie = n + "=" + t + f
- }
- }
- function c(e, t) {
- if ("undefined" != typeof document) {
- for (var r = {}, i = document.cookie ? document.cookie.split("; ") : [], c = 0; c < i.length; c++) {
- var f = i[c].split("=")
- , u = f.slice(1).join("=");
- t || '"' !== u.charAt(0) || (u = u.slice(1, -1));
- try {
- var a = n(f[0]);
- if (u = (o.read || o)(u, a) || n(u),
- t)
- try {
- u = JSON.parse(u)
- } catch (e) {}
- if (r[a] = u,
- e === a)
- break
- } catch (e) {}
- }
- return e ? r[e] : r
- }
- }
- return r.set = i,
- r.get = function(e) {
- return c(e, !1)
- }
- ,
- r.getJSON = function(e) {
- return c(e, !0)
- }
- ,
- r.remove = function(n, t) {
- i(n, "", e(t, {
- expires: -1
- }))
- }
- ,
- r.defaults = {},
- r.withConverter = t,
- r
- }(function() {})
-});
-
-$(function() {
- var result = location.search.match(/\?search=(\w*)&?/i)
- if (result && result[1]) {
- var searchText = result[1]
- $('#search-input').val(searchText)
- }
- // 未报名用户登录时弹框
- // console.log(Cookies.get('enroll_status'));
- // if(Cookies.get('enroll_status') == 0){
- // Cookies.remove('enroll_status');
- // var html='
'+
- // '
'+
- // '
'+
- // '
'+
- // '
立即报名'+
- // '
';
- // $(".newContainer").append(html);
- // }
-});
-
-function CloseBox() {
- $(".CompetitionEnrollBox").remove();
-}
-
-//根据页面大小决定侧边栏的位置
-$(window).resize(function() {
- rightSlider();
-});
-function rightSlider() {
- var poi = parseInt((parseInt($(window).width()) - 1200) / 2) - 60;
- // console.log(parseInt($(window).width())+" "+poi);
- if (poi > 0) {
- $(".-task-sidebar").css("right", poi);
- } else {
- $(".-task-sidebar").css("right", "0px");
- }
- $(".-task-sidebar").show();
-}
-function open_course(id, allowVisit) {
- if (allowVisit) {
- window.open("/courses/" + id);
- }
-}
-function open_project(id, allowVisit) {
- if (allowVisit) {
- window.open("/projects/" + id);
- }
-}
-
-function conver_size(limit) {
- var size = "";
- if (limit < 1024) {
- //如果小于1KB转化成B
- size = limit.toFixed(2) + "B";
- } else if (limit < 1024 * 1024) {
- //如果小于1MB转化成KB
- size = (limit / 1024).toFixed(2) + "KB";
- } else if (limit < 1024 * 1024 * 1024) {
- //如果小于1GB转化成MB
- size = (limit / (1024 * 1024)).toFixed(2) + "MB";
- } else {
- //其他转化成GB
- size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB";
- }
-
- var sizestr = size + "";
- var len = sizestr.indexOf("\.");
- var dec = sizestr.substr(len + 1, 2);
- if (dec == "00") {
- //当小数点后为00时 去掉小数部分
- return sizestr.substring(0, len) + sizestr.substr(len + 3, 2);
- }
- return sizestr;
-}
-
-function _initSider() {
- var $descSide = $("").appendTo("body");
- $(".-task-sidebar>div").hover(function() {
- //移入显示二维码
- if ($(this).hasClass("scan")) {
- $(".scan_ewm").show().css({
- right: "75px",
- opacity: 0
- }).stop().animate({
- right: "45px",
- opacity: 1
- })
- return;
- }
- var $tool = $(this).attr("tooltips");
- $descSide.html($tool + "");
- $descSide.data('_dom', this)
- $descSide.show().css({
- left: $(this).offset().left - $descSide.width() - 30,
- opacity: 0,
- top: $(this).offset().top
- }).stop().animate({
- left: $(this).offset().left - $descSide.width() - 5,
- opacity: 1
- }, 400);
- }, function() {
- if ($(this).hasClass("scan")) {
- $(".scan_ewm").stop().animate({
- right: "75px",
- opacity: 0
- }, 200).hide();
- }
- $descSide.stop().animate({
- left: $(this).offset().left - $descSide.width() - 30,
- opacity: 0
- }, 200).hide();
- });
- rightSlider();
-
- $(window).scroll(function() {
- if ($descSide.height()) {
- var hoverIcon = $descSide.data('_dom')
- $descSide.css('top', $(hoverIcon).offset().top)
- }
- })
-}
-$(function() {
- loadHeader();
- _initSider();
-
- $(window).scroll(function() {
- if ($(".gotop").length > 0) {
- if ($(document).scrollTop() > 0) {
- $(".-task-sidebar .gotop").show();
- $(".gotop").click(function() {
- $("html,body").scrollTop(0);
- });
- }
- if ($(document).scrollTop() == 0) {
- $(".-task-sidebar .gotop").hide();
- }
- }
- });
-
- // 翻页的GO
- $(".page_GO").live("keydown", function(event) {
- var code;
- if (!event) {
- event = window.event;
- //针对ie浏览器
- code = event.keyCode;
- } else {
- code = event.keyCode;
- }
- if (code == 13) {
- var prev = $(this).prev().find("a").html().trim();
- var page = $(this).val().trim();
- if (parseInt(prev) >= parseInt(page)) {
- if (typeof ($(this).prev().children("a").attr("href")) == "undefined") {
- var href = $(this).parent().find("li:first-child").children("a").attr("href");
- } else {
- var href = $(this).prev().children("a").attr("href");
- }
- var new_href = href.replace(/page=(\d*)/, 'page=' + page);
- console.log(new_href);
- $.get(new_href);
- return false;
- }
- }
- });
-
- // 试用申请弹框
- $("#apply_trail_submit_btn").live('click', function() {
- if ($("#apply_reason").val().trim() == "") {
- $("#hint_message").show();
- } else {
- $("#hint_message").hide();
- $("#apply_trail_form").submit();
- hideModal();
- }
- });
-
-});
-
-// editor 存在了jquery对象上,应用不需要自己写md_rec_data方法了
-function md_rec_data(k, mdu, id) {
- if (window.sessionStorage.getItem(k + mdu) !== null) {
- editor = $("#e_tips_" + id).data('editor');
- editor.setValue(window.sessionStorage.getItem(k + mdu));
-
- md_clear_data(k, mdu, id);
- }
-}
-// markdown的自动保存
-function md_elocalStorage(editor, mdu, id) {
- if (window.sessionStorage) {
- var oc = window.sessionStorage.getItem('content' + mdu);
- if (oc !== null) {
- $("#e_tips_" + id).data('editor', editor);
- var h = '您上次有已保存的数据,是否恢复 ? / 不恢复';
- $("#e_tips_" + id).html(h);
- }
- setInterval(function() {
- d = new Date();
- var h = d.getHours();
- var m = d.getMinutes();
- var s = d.getSeconds();
- h = h < 10 ? '0' + h : h;
- m = m < 10 ? '0' + m : m;
- s = s < 10 ? '0' + s : s;
- if (editor.getValue().trim() != "") {
- md_add_data("content", mdu, editor.getValue());
- var id1 = "#e_tip_" + id;
- var id2 = "#e_tips_" + id;
- $(id1).html(" 数据已于 " + h + ':' + m + ':' + s + " 保存 ");
- $(id2).html("");
- }
- }, 10000);
-
- } else {
- $("#e_tip_" + id).after('您的浏览器不支持localStorage.无法开启自动保存草稿服务,请升级浏览器!');
- }
-}
-// 保存数据
-function md_add_data(k, mdu, d) {
- window.sessionStorage.setItem(k + mdu, d);
-}
-// 恢复数据
-//function md_rec_data(k,mdu,id, editor){
-// if(window.sessionStorage.getItem(k+mdu) !== null){
-// editor.setValue(window.sessionStorage.getItem(k+mdu));
-// md_clear_data(k,mdu,id);
-// }
-//}
-// 清空保存的数据
-function md_clear_data(k, mdu, id) {
- window.sessionStorage.removeItem(k + mdu);
- var id1 = "#e_tip_" + id;
- var id2 = "#e_tips_" + id;
- if (k == 'content') {
- $(id2).html("");
- } else {
- $(id1).html("");
- }
-}
-
-// editorMD to create
-/**
- *
- * @param id 渲染DOM的id
- * @param width 宽度
- * @param high 高度
- * @param placeholder
- * @param imageUrl 上传图片的url
- * @returns {*} 返回一个editorMD实例
- */
-function create_editorMD(id, width, high, placeholder, imageUrl, readonly) {
- var readonly = readonly == undefined ? false : readonly;
- var editorName = editormd(id, {
- width: width,
- height: high,
- syncScrolling: "single",
- //你的lib目录的路径,我这边用JSP做测试的
- path: "/editormd/lib/",
- tex: true,
- tocm: true,
- emoji: true,
- taskList: true,
- codeFold: true,
- searchReplace: true,
- htmlDecode: "style,script,iframe",
- sequenceDiagram: true,
- autoFocus: false,
- readonly: readonly,
- toolbarIcons: function() {
- // Or return editormd.toolbarModes[name]; // full, simple, mini
- // Using "||" set icons align right.
- return ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "|", "testIcon", "testIcon1", '|', "image", "table", '|', "watch", "clear"]
- },
- toolbarCustomIcons: {
- testIcon: "",
- testIcon1: ""
- },
- //这个配置在simple.html中并没有,但是为了能够提交表单,使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中,方便post提交表单。
- saveHTMLToTextarea: true,
- // 用于增加自定义工具栏的功能,可以直接插入HTML标签,不使用默认的元素创建图标
- dialogMaskOpacity: 0.6,
- placeholder: placeholder,
- imageUpload: true,
- imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
- imageUploadURL: imageUrl,
- //url
- onload: function(cMirror) {
- $("#" + id + " [type=\"latex\"]").bind("click", function() {
- editorName.cm.replaceSelection("```latex");
- editorName.cm.replaceSelection("\n");
- editorName.cm.replaceSelection("\n");
- editorName.cm.replaceSelection("```");
- var __Cursor = editorName.cm.getDoc().getCursor();
- editorName.cm.setCursor(__Cursor.line - 1, 0);
- });
-
- $("#" + id + " [type=\"inline\"]").bind("click", function() {
- editorName.cm.replaceSelection("$$$$");
- var __Cursor = editorName.cm.getDoc().getCursor();
- editorName.cm.setCursor(__Cursor.line, __Cursor.ch - 2);
- editorName.cm.focus();
- });
- $("[type=\"inline\"]").attr("title", "行内公式");
- $("[type=\"latex\"]").attr("title", "多行公式");
- setTimeout(function() {
- editorName.resize();
- editorName.cm.refresh();
- window.new_md = editorName;
- }, 300);
- }
- });
- return editorName;
-}
-
-// editormd to html
-/**
- *
- * @param id 渲染的id
- * @param callback onload回調 暫時未用
- */
-function editormd_to_html(id, callback) {
- editormd.loadKaTeX(function() {
- editormd.markdownToHTML(id, {
- htmlDecode: "style,script,iframe",
- // you can filter tags decode
- onload: function() {
- callback && callback()
- },
- taskList: true,
- tex: true,
- // 默认不解析
- flowChart: true,
- // 默认不解析
- sequenceDiagram: true// 默认不解析
- });
- });
-}
-
-function loadHeader() {
- //头部导航条的----------显示搜索框
- $("#search-open").on("click", function(e) {
- $(this).hide();
- // $("#header-nav").animate({opacity:"0"},1000);
- $(".posi-search").show()
- // .animate({opacity:"1"},1000);
- $("#header-nav").css("z-index", "2");
- $(".posi-search").css("z-index", "3");
- // $(".search-input").val(""); // 不清空
- $(".search-input").focus();
- $(".search-all .search-content").hide();
- e.stopPropagation();
- //阻止冒泡
- });
- $(".search-input").on("click", function(e) {
- e.stopPropagation();
- //阻止冒泡
- });
- //搜索框输入内容
- $(".search-input").on("input", function(e) {
- if ($(".search-input").val() == "") {
- $(".search-all .search-content").hide();
- } else {
- $(".search-all .search-content").show();
- }
- e.stopPropagation();
- //阻止冒泡
- });
- //搜索
- $("#header_keyword_search").on("click", header_search);
- $("input[name='search_keyword']").on("keydown", function(event) {
- var code;
- if (!event) {
- event = window.event;
- //针对ie浏览器
- code = event.keyCode;
- } else {
- code = event.keyCode;
- }
- if (code == 13) {
- header_search();
- return false;
- }
- });
- $(".search-clear").click(function(e) {
- e.stopPropagation();
- });
- //切换搜索条件
- $("#searchkey li").click(function(e) {
- var key = $($(this).children("a")[0]).html();
- switch (key) {
- case '实训':
- $("#search_type").val('1');
- break;
- case '课堂':
- $("#search_type").val('2');
- break;
- case '用户':
- $("#search_type").val('3');
- break;
- }
- $("#searchkey").siblings(".searchkey").html(key);
- // $("#searchkey").hide();
- e.stopPropagation();
- //阻止冒泡
- });
- //切换选择导航条
- $("#header-nav li").click(function() {
- $("#header-nav li").removeClass("active");
- $(this).addClass("active");
- });
- //点击页面其它(与搜索框无关的地方)都会将搜索框隐藏,所以与搜索框有关的地方需要阻止冒泡
- $("body").on("click", function() {
- closeSearch();
- });
-
- $(".search_history").on("click", function() {
- $("input[name='search_keyword']").val($(this).html());
- header_search();
- });
-}
-
-function header_search() {
- var keyword = $("input[name='search_keyword']").val();
- // 搜索关键字
- var index = $("#search_type").val();
- // 搜索课程/项目
- keyword = encodeURIComponent(keyword);
- // $.get('/users/search_shixuns_or_course',
- // { search: keyword,
- // index: index});
- window.location.href = "/users/search_shixuns_or_courses" + "?search=" + keyword + "&index=" + index;
- //e.stopPropagation();//阻止冒泡
-}
-
-//头部导航条的隐藏
-function closeSearch() {
- $('#posi-search').hide();
- $("#search-open").show();
- // $(".posi-search").animate({opacity:"0"},800);
- $("#header-nav").animate({
- opacity: "1"
- }, 1000);
- $(".posi-search").css("z-index", "2");
- $("#header-nav").css("z-index", "3");
-}
-(function($) {
- $.fn.drag = function(options) {
- var x, drag = this, isMove = false, defaults = {};
- var options = $.extend(defaults, options);
- //添加背景,文字,滑块
- var html = '' + '请拖住滑块,拖动到最右边
' + '';
- this.append(html);
-
- var handler = drag.find('.handler');
- var drag_bg = drag.find('.drag_bg');
- var text = drag.find('.drag_text');
- var maxWidth = text.width() - handler.width();
- //能滑动的最大间距
- //鼠标按下时候的x轴的位置
- handler.mousedown(function(e) {
- isMove = true;
- x = e.pageX - parseInt(handler.css('left'), 10);
- });
-
- //鼠标指针在上下文移动时,移动距离大于0小于最大间距,滑块x轴位置等于鼠标移动距离
- $(document).mousemove(function(e) {
- var _x = e.pageX - x;
- var handler_offset = handler.offset();
- var lastX = e.clientX - x;
- lastX = Math.max(0, Math.min(maxWidth, lastX));
- if (isMove) {
- if (_x > 0 && _x <= maxWidth) {
- handler.css({
- 'left': lastX
- });
- drag_bg.css({
- 'width': lastX
- });
- } else if (lastX > maxWidth - 5 && lastX < maxWidth + 5) {
- //鼠标指针移动距离达到最大时清空事件
- dragOk();
- }
- }
- });
- handler.mouseup(function(e) {
- isMove = false;
- var _x = e.pageX - x;
- if (_x < maxWidth) {
- //鼠标松开时,如果没有达到最大距离位置,滑块就返回初始位置
- handler.css({
- 'left': 0
- });
- drag_bg.css({
- 'width': 0
- });
- }
- });
-
- //清空事件
- function dragOk() {
- var kuaiwidth = drag.width() - handler.width();
- handler.removeClass('handler_bg').addClass('handler_ok_bg');
- handler.css({
- 'left': kuaiwidth + 'px'
- })
- text.css({
- 'width': kuaiwidth + 'px'
- });
- text.text('验证通过');
- drag.css({
- 'color': '#fff'
- });
- drag_bg.css({
- 'width': kuaiwidth + 'px'
- })
- handler.unbind('mousedown');
- $(document).unbind('mousemove');
- $(document).unbind('mouseup');
- handler.parent().next().find("p").html("").hide();
- }
- }
- ;
-}
-)(jQuery);
-
-//判断是手机端还是电脑端
-function IsPC() {
- var userAgentInfo = navigator.userAgent;
- var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
- var flag = true;
- for (var v = 0; v < Agents.length; v++) {
- if (userAgentInfo.indexOf(Agents[v]) > 0) {
- flag = false;
- break;
- }
- }
- return flag;
-}
-
-//Dom:绑定事件的节点对象,ChangeDOM:操作的相关节点,
-function LeaveTitle(Dom, ChangeDom) {
- ChangeDom.html("").hide();
- ChangeDom.parent().css({
- opacity: 0,
- left: 0,
- top: 0
- }).hide();
-}
-
-$(function() {
- //平台tip的样式优化js
- var $desc = $("" + "
" + "
" + "
" + "
" + "
").appendTo("body");
- //Dom:绑定事件的节点对象,ChangeDOM:操作的相关节点,
- function LeaveTitle(Dom, ChangeDom) {
- Dom.live("mouseleave", function() {
- ChangeDom.html("").hide();
- $desc.css({
- opacity: 0,
- left: 0,
- top: 0
- }).hide();
- })
- }
- LeaveTitle($("[data-tip-top]"), $(".data-tip-top"));
- LeaveTitle($("[data-tip-down]"), $(".data-tip-down"));
- LeaveTitle($("[data-tip-right]"), $(".data-tip-left"));
- LeaveTitle($("[data-tip-left]"), $(".data-tip-right"));
- $("[data-tip-top]").live("mouseenter", function() {
- var $tool = $(this).attr("data-tip-top");
- if ($tool != "") {
- $(".data-tip-top").show().html($tool);
- $desc.show().css({
- left: $(this).offset().left - ($desc.width() - $(this).outerWidth()) / 2,
- opacity: 1,
- top: $(this).offset().top - 30
- });
- }
- });
- $("[data-tip-down]").live("mouseenter", function() {
- var $tool = $(this).attr("data-tip-down");
- if ($tool != "") {
- $(".data-tip-down").show().html($tool);
- $desc.show().css({
- left: $(this).offset().left - ($desc.width() - $(this).outerWidth()) / 2,
- opacity: 1,
- top: $(this).offset().top + $(this).height() + 6
- });
- }
- });
- $("[data-tip-right]").live("mouseenter", function() {
- var $tool = $(this).attr("data-tip-right");
- if ($tool != "") {
- console.log($(this).offset().left + " " + $(this).width());
- $(".data-tip-left").show().html($tool);
- $desc.show().css({
- left: $(this).offset().left + $(this).outerWidth() + 6,
- opacity: 1,
- top: $(this).offset().top - ($desc.height() - $(this).height()) / 2
- });
- }
- });
- $("[data-tip-left]").live("mouseenter", function() {
- var $tool = $(this).attr("data-tip-left");
- if ($tool != "") {
- $(".data-tip-right").show().html($tool);
- $desc.show().css({
- left: $(this).offset().left - $desc.width() - 6,
- opacity: 1,
- top: $(this).offset().top - ($desc.height() - $(this).height()) / 2
- });
- }
- });
- unitDownOption();
-});
-
-function unitDownOption() {
- //下拉框
- $("[select-for]").append("");
- $("[select-for]").hover(function() {
- $(this).find(".down-select").show();
- }, function() {
- $(this).find(".down-select").hide();
- })
- $("[select-for] .down-select p").bind("click", function() {
- //alert($(this).attr("data-shixun-value"));
- if ($(this).attr("id") == "diy_script") {
- return;
- // 实训新建-选择自定义脚本diy
- }
- $(this).parents(".down-select").siblings("input[type=hidden]").attr("value", $(this).attr("data-shixun-value"));
-
- $(this).parents(".down-select").siblings("input[type=text]").val($(this).html().trim());
- $(this).parents(".down-select").hide();
- })
-}
-
-//初始化省份
-function showprovince(id) {
- var arrary = ["北京", "上海", "广东", "江苏", "浙江", "重庆", "安徽", "福建", "甘肃", "广西", "贵州", "海南", "河北", "黑龙江", "河南", "湖北", "湖南", "江西", "吉林", "辽宁", "内蒙古", "宁夏", "青海", "山东", "山西", "陕西", "四川", "天津", "新疆", "西藏", "云南", "香港特别行政区", "澳门特别行政区", "台湾", "海外"];
- var html = ""
- for (var i = 0; i < arrary.length; i++) {
- var item = arrary[i];
- html += "";
- }
- $("#" + id).html(html);
-}
-
-//省市下拉框
-function showcity(province, cityField) {
- switch (province) {
- case "北京":
- var cityOptions = new Array("东城","西城","朝阳","丰台","石景山","海淀","门头沟","房山","通州","顺义","昌平","大兴","平谷","怀柔","密云","延庆");
- break;
- case "上海":
- var cityOptions = new Array("崇明","黄浦","卢湾","徐汇","长宁","静安","普陀","闸北","虹口","杨浦","闵行","宝山","嘉定","浦东","金山","松江","青浦","南汇","奉贤");
- break;
- case "广东":
- var cityOptions = new Array("广州","深圳","珠海","东莞","中山","佛山","惠州","河源","潮州","江门","揭阳","茂名","梅州","清远","汕头","汕尾","韶关","顺德","阳江","云浮","湛江","肇庆");
- break;
- case "江苏":
- var cityOptions = new Array("南京","常熟","常州","海门","淮安","江都","江阴","昆山","连云港","南通","启东","沭阳","宿迁","苏州","太仓","泰州","同里","无锡","徐州","盐城","扬州","宜兴","仪征","张家港","镇江","周庄");
- break;
- case "重庆":
- var cityOptions = new Array("万州","涪陵","渝中","大渡口","江北","沙坪坝","九龙坡","南岸","北碚","万盛","双挢","渝北","巴南","黔江","长寿","綦江","潼南","铜梁","大足","荣昌","壁山","梁平","城口","丰都","垫江","武隆","忠县","开县","云阳","奉节","巫山","巫溪","石柱","秀山","酉阳","彭水","江津","合川","永川","南川");
- break;
- case "安徽":
- var cityOptions = new Array("合肥","安庆","蚌埠","亳州","巢湖","滁州","阜阳","贵池","淮北","淮化","淮南","黄山","九华山","六安","马鞍山","宿州","铜陵","屯溪","芜湖","宣城");
- break;
- case "福建":
- var cityOptions = new Array("福州","厦门","泉州","漳州","龙岩","南平","宁德","莆田","三明");
- break;
- case "甘肃":
- var cityOptions = new Array("兰州","白银","定西","敦煌","甘南","金昌","酒泉","临夏","平凉","天水","武都","武威","西峰","张掖");
- break;
- case "广西":
- var cityOptions = new Array("南宁","百色","北海","桂林","防城港","贵港","河池","贺州","柳州","钦州","梧州","玉林");
- break;
- case "贵州":
- var cityOptions = new Array("贵阳","安顺","毕节","都匀","凯里","六盘水","铜仁","兴义","玉屏","遵义");
- break;
- case "海南":
- var cityOptions = new Array("海口","儋县","陵水","琼海","三亚","通什","万宁");
- break;
- case "河北":
- var cityOptions = new Array("石家庄","保定","北戴河","沧州","承德","丰润","邯郸","衡水","廊坊","南戴河","秦皇岛","唐山","新城","邢台","张家口");
- break;
- case "黑龙江":
- var cityOptions = new Array("哈尔滨","北安","大庆","大兴安岭","鹤岗","黑河","佳木斯","鸡西","牡丹江","齐齐哈尔","七台河","双鸭山","绥化","伊春");
- break;
- case "河南":
- var cityOptions = new Array("郑州","安阳","鹤壁","潢川","焦作","济源","开封","漯河","洛阳","南阳","平顶山","濮阳","三门峡","商丘","新乡","信阳","许昌","周口","驻马店");
- break;
- case "香港":
- var cityOptions = new Array("香港","九龙","新界");
- break;
- case "湖北":
- var cityOptions = new Array("武汉","恩施","鄂州","黄冈","黄石","荆门","荆州","潜江","十堰","随州","武穴","仙桃","咸宁","襄阳","襄樊","孝感","宜昌");
- break;
- case "湖南":
- var cityOptions = new Array("长沙","常德","郴州","衡阳","怀化","吉首","娄底","邵阳","湘潭","益阳","岳阳","永州","张家界","株洲");
- break;
- case "江西":
- var cityOptions = new Array("南昌","抚州","赣州","吉安","景德镇","井冈山","九江","庐山","萍乡","上饶","新余","宜春","鹰潭");
- break;
- case "吉林":
- var cityOptions = new Array("长春","吉林","白城","白山","珲春","辽源","梅河","四平","松原","通化","延吉");
- break;
- case "辽宁":
- var cityOptions = new Array("沈阳","鞍山","本溪","朝阳","大连","丹东","抚顺","阜新","葫芦岛","锦州","辽阳","盘锦","铁岭","营口");
- break;
- case "澳门":
- var cityOptions = new Array("澳门");
- break;
- case "内蒙古":
- var cityOptions = new Array("呼和浩特","阿拉善盟","包头","赤峰","东胜","海拉尔","集宁","临河","通辽","乌海","乌兰浩特","锡林浩特");
- break;
- case "宁夏":
- var cityOptions = new Array("银川","固源","石嘴山","吴忠");
- break;
- case "青海":
- var cityOptions = new Array("西宁","德令哈","格尔木","共和","海东","海晏","玛沁","同仁","玉树");
- break;
- case "山东":
- var cityOptions = new Array("济南","滨州","兖州","德州","东营","菏泽","济宁","莱芜","聊城","临沂","蓬莱","青岛","曲阜","日照","泰安","潍坊","威海","烟台","枣庄","淄博");
- break;
- case "山西":
- var cityOptions = new Array("太原","长治","大同","候马","晋城","离石","临汾","宁武","朔州","忻州","阳泉","榆次","运城");
- break;
- case "陕西":
- var cityOptions = new Array("西安","安康","宝鸡","汉中","渭南","商州","绥德","铜川","咸阳","延安","榆林");
- break;
- case "四川":
- var cityOptions = new Array("成都","巴中","达川","德阳","都江堰","峨眉山","涪陵","广安","广元","九寨沟","康定","乐山","泸州","马尔康","绵阳","眉山","南充","内江","攀枝花","遂宁","汶川","西昌","雅安","宜宾","自贡","资阳");
- break;
- case "台湾":
- var cityOptions = new Array("台北","基隆","台南","台中","高雄","屏东","南投","云林","新竹","彰化","苗栗","嘉义","花莲","桃园","宜兰","台东","金门","马祖","澎湖");
- break;
- case "天津":
- var cityOptions = new Array("天津","和平","东丽","河东","西青","河西","津南","南开","北辰","河北","武清","红挢","塘沽","汉沽","大港","宁河","静海","宝坻","蓟县");
- break;
- case "新疆":
- var cityOptions = new Array("乌鲁木齐","阿克苏","阿勒泰","阿图什","博乐","昌吉","东山","哈密","和田","喀什","克拉玛依","库车","库尔勒","奎屯","石河子","塔城","吐鲁番","伊宁");
- break;
- case "西藏":
- var cityOptions = new Array("拉萨","阿里","昌都","林芝","那曲","日喀则","山南");
- break;
- case "云南":
- var cityOptions = new Array("昆明","大理","保山","楚雄","大理","东川","个旧","景洪","开远","临沧","丽江","六库","潞西","曲靖","思茅","文山","西双版纳","玉溪","中甸","昭通");
- break;
- case "浙江":
- var cityOptions = new Array("杭州","安吉","慈溪","定海","奉化","海盐","黄岩","湖州","嘉兴","金华","临安","临海","丽水","宁波","瓯海","平湖","千岛湖","衢州","江山","瑞安","绍兴","嵊州","台州","温岭","温州","余姚","舟山");
- break;
- case "海外":
- var cityOptions = new Array("美国","日本","英国","法国","德国","其他");
- break;
- default:
- var cityOptions = new Array("请选择所在城市");
- break;
- }
-
- cityField.options.length = 0;
- for (var i = 0; i < cityOptions.length; i++) {
- cityField.options[i] = new Option(cityOptions[i],cityOptions[i]);
- /*
- if (cityField.options[i].value==city)
- {
- //alert("here put City ok!");
- document.oblogform["city"].selectedIndex = i;
- }*/
- }
-}
-
-/*弹框*/
-// 公共弹框样式
-// 建议左右栏的:Width:460,Height:190
-// 建议宽屏对应值:Width:760,Height:500
-function pop_box_new(value, Width, Height, close) {
-
- if ($("#popupAll").length > 0) {
- $("#popupAll").remove();
- }
- w = ($(window).width() - Width) / 2;
- h = ($(window).height() - Height) / 2;
- var html = "";
- if (close) {
- value = "" + value;
- }
- $(document.body).append(html);
- $("#popupWrap").html(value);
- $('#popupWrap').css({
- "top": h + "px",
- "left": w + "px",
- "padding": "0",
- "border": "none",
- "position": "fixed",
- "z-index": "99999",
- "background-color": "#fff",
- "border-radius": "10px"
- });
- if (close) {
- $('#closeIcon').css({
- "top": "-26px",
- "left": Width + "px",
- "z-index": "100000"
- });
- }
-
- $("#popupWrap").parent().parent().show();
- $('#popupAll').find("#closeIcon").click(function() {
- $("#popupAll").hide();
- });
- $('#popupAll').find("a[class*='pop_close']").click(function() {
- $("#popupAll").hide();
- });
- // w = ($(window).width() - Width)/2;
- // h = ($(window).height() - Height)/2;
- // $("#ajax-modal").html(value);
- // showModal('ajax-modal', Width + 'px');
- // $('#ajax-modal').siblings().remove();
- // $('#ajax-modal').parent().css({"top": h+"px","left": w+"px","padding":"0","border":"none","position":"fixed"});
- // $('#ajax-modal').parent().removeClass("resourceUploadPopup popbox_polls popbox");
- // $('#ajax-modal').css({"padding":"0","overflow":"hidden"});
- // $('#ajax-modal').parent().attr("id","popupWrap");
-
- //拖拽
- function Drag(id) {
- this.div = document.getElementById(id);
- if (this.div) {
- this.div.style.cursor = "move";
- this.div.style.position = "fixed";
- }
- this.disX = 0;
- this.disY = 0;
- var _this = this;
- this.div.onmousedown = function(evt) {
- _this.getDistance(evt);
- document.onmousemove = function(evt) {
- _this.setPosition(evt);
- }
- ;
- _this.div.onmouseup = function() {
- _this.clearEvent();
- }
- }
- }
- Drag.prototype.getDistance = function(evt) {
- var oEvent = evt || event;
- this.disX = oEvent.clientX - this.div.offsetLeft;
- this.disY = oEvent.clientY - this.div.offsetTop;
- }
- ;
- Drag.prototype.setPosition = function(evt) {
- var oEvent = evt || event;
- var l = oEvent.clientX - this.disX;
- var t = oEvent.clientY - this.disY;
- if (l <= 0) {
- l = 0;
- } else if (l >= document.documentElement.clientWidth - this.div.offsetWidth) {
- l = document.documentElement.clientWidth - this.div.offsetWidth;
- }
- if (t <= 0) {
- t = 0;
- } else if (t >= document.documentElement.clientHeight - this.div.offsetHeight) {
- t = document.documentElement.clientHeight - this.div.offsetHeight;
- }
- this.div.style.left = l + "px";
- this.div.style.top = t + "px";
- }
- ;
- Drag.prototype.clearEvent = function() {
- this.div.onmouseup = null;
- document.onmousemove = null;
- }
- ;
-
- new Drag("popupWrap");
-
- $("#popupAll input, #popupAll textarea, #popupAll select, #popupAll ul, #popupAll a,#shixun_search_form_div").mousedown(function(event) {
- event.stopPropagation();
- new Drag("popupWrap");
- });
-
-}
-
-function hideModal(el) {
- if ($("#popupAll").length > 0) {
- $("#popupAll").remove();
- } else {
- var modal;
- if (el) {
- modal = $(el).parents('.ui-dialog-content');
- } else {
- modal = $('#ajax-modal');
- }
- modal.dialog("close");
- }
-}
-
-//提示框:只有一个确定按钮,点击跳转
-//
-function notice_box_redirect(url, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 480, 160);
-}
-//按钮内容自定义(自定义按钮需要remote=true,且有取消按钮)
-function notice_operation_box(url, str, btnstr) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 480, 160);
-}
-//点击删除时的确认弹框: 不走destroy方法
-function delete_confirm_box(url, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 480, 160);
-}
-//点击删除时的确认弹框: 走destroy方法,remote为true
-function delete_confirm_box_2(url, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 480, 160);
-}
-
-// 点击确定的时候ajax请求,两个按钮 点击确认跳转, 提示信息可以多行
-function op_confirm_box_remote(url, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 578, 205);
-}
-
-//点击删除时的确认弹框: post,remote为true
-function post_confirm_box(url, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 480, 160);
-}
-
-//提示框:只有一个确定按钮,点击关闭弹框
-//
-function notice_box(str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 480, 160);
-}
-
-//点击删除时的确认弹框: 走destroy方法
-function delete_confirm_box_3(url, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 480, 160);
-}
-
-//取消和确定,确定会调用自定义方法
-function op_confirm_tip(str, func) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 500, 205);
-}
-
-//取消和确定,确定会调用自定义方法(带参数)
-function op_confirm_tip_1(str, func) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 500, 205);
-}
-
-function op_confirm_box_loading(url, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 578, 205);
-}
-
-// 两个按钮 点击确认跳转, 提示信息有两行
-function s_op_confirm_box(url, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, 480, 205);
-}
-
-function suofang() {
- var html = '可能会影响某些功能的正常使用
' + '- 1.请尝试调整浏览器缩放比例为100%(快捷键ctrl+0)
' + '- 2.请尝试调整系统显示比例为100%(控制面板/显示 设置)
' + '
';
- sure_confirm_box("页面缩放比例不正确", 600, 310, html);
-}
-
-//一个“知道了”按钮,title和宽度都作为参数
-function sure_confirm_box(title, width, height, str) {
- var htmlvalue = '';
- pop_box_new(htmlvalue, width, height);
-}
-
-function throttle(method, context, e) {
- clearTimeout(method.tId);
- method.tId = setTimeout(function() {
- method.call(context, e);
- }, 500);
-}
-
-function apply_publish_shixun(url) {
- if ($("#apply_publish_shixun").attr("data-option") == '1') {
- $("#apply_publish_shixun").attr("data-option", 0);
- $("#apply_publish_shixun").addClass("disabled-grey-bg");
- $.ajax({
- url: url,
- type: 'get'
- });
- }
-}
-
-var autoTextarea = function(elem, extra, maxHeight) {
- extra = extra || 0;
- var isFirefox = !!document.getBoxObjectFor || 'mozInnerScreenX'in window
- , isOpera = !!window.opera && !!window.opera.toString().indexOf('Opera')
- , addEvent = function(type, callback) {
- elem.addEventListener ? elem.addEventListener(type, callback, false) : elem.attachEvent('on' + type, callback);
- }
- , getStyle = elem.currentStyle ? function(name) {
- var val = elem.currentStyle[name];
-
- if (name === 'height' && val.search(/px/i) !== 1) {
- var rect = elem.getBoundingClientRect();
- return rect.bottom - rect.top - parseFloat(getStyle('paddingTop')) - parseFloat(getStyle('paddingBottom')) + 'px';
- }
- ;
- return val;
- }
- : function(name) {
- return getComputedStyle(elem, null)[name];
- }
- , minHeight = parseFloat(getStyle('height'));
-
- elem.style.resize = 'none';
-
- var change = function() {
- var scrollTop, height, padding = 0, style = elem.style;
-
- if (elem._length === elem.value.length)
- return;
- elem._length = elem.value.length;
-
- if (!isFirefox && !isOpera) {
- padding = parseInt(getStyle('paddingTop')) + parseInt(getStyle('paddingBottom'));
- }
- ;scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
-
- elem.style.height = minHeight + 'px';
- if (elem.scrollHeight > minHeight) {
- if (maxHeight && elem.scrollHeight > maxHeight) {
- height = maxHeight - padding;
- style.overflowY = 'auto';
- } else {
- height = elem.scrollHeight - padding + 10;
- style.overflowY = 'hidden';
- }
- ;style.height = height + extra + 'px';
- scrollTop += parseInt(style.height) - elem.currHeight;
- //document.body.scrollTop = scrollTop;
- //document.documentElement.scrollTop = scrollTop;
- elem.currHeight = parseInt(style.height);
- }
- ;
- };
-
- addEvent('propertychange', change);
- addEvent('input', change);
- addEvent('focus', change);
- change();
-};
-
-// 点击按钮复制功能
-function jsCopy() {
- var e = document.getElementById("copy_rep_content");
- e.select();
- document.execCommand("Copy");
-}
-
-// 使用resize事件监听窗口的zoom,如果zoom变化了,弹框提示;初始化时也检查zoom是否是100%。
-function _initZoomCheck() {
- if (!IsPC()) {
- // 手机端不需要提示
- return;
- }
- var isNormalZoom = Math.round(window.devicePixelRatio * 100) === 100
- if (!isNormalZoom) {
- suofang();
- }
-
- $(window).resize(function() {
- var isNormalZoom = Math.round(window.devicePixelRatio * 100) === 100
- if (!isNormalZoom) {
- suofang();
- } else {
- $('.task-btn.task-btn-orange:visible').click()
- }
- })
-
-}
-
-var win_resize = function() {
- var _w = $(window).width() - 1200;
- if (_w < 0) {
- $('.newHeader>.educontent').width('auto')
- } else {
- $('.newHeader>.educontent').width('1200px')
- }
-};
-
-function initWindowResize() {
- if (location.pathname === '/login') {
- // 登录页不需要
- return;
- }
-
- $(function() {
- setTimeout(function() {
- win_resize();
- }, 1000)
- })
-
- $(window).resize(function() {
- win_resize()
- })
-}
-initWindowResize();
-
-// 登录刷新 https://stackoverflow.com/questions/28230845/communication-between-tabs-or-windows
-if (window['BroadcastChannel']) {
- var bc = new BroadcastChannel('ec_reload');
- bc.onmessage = function(ev) {
- if (window['ec_reload_msg_send_window']) {
- window['ec_reload_msg_send_window'] = false;
- } else {
- location.reload();
- }
- }
-}
-function _sendReloadMsg() {
- var bc = new BroadcastChannel('ec_reload');
- window['ec_reload_msg_send_window'] = true;
- // 消息发出的窗口,不需要处理该消息
- bc.postMessage('ec_reload');
- /* send */
-}
-// IE11 没有 startsWith
-if (!String.prototype.startsWith) {
- String.prototype.startsWith = function(searchString, position) {
- position = position || 0;
- return this.substr(position, searchString.length) === searchString;
- }
- ;
- String.prototype.endsWith = function(search, this_len) {
- if (this_len === undefined || this_len > this.length) {
- this_len = this.length;
- }
- return this.substring(this_len - search.length, this_len) === search;
- }
- ;
-}
-
-function clickNewsubscript() {
- $(".newsubscript").hide();
- $(".newedbox").addClass("newminheight");
- $(".newedbox").removeClass("newedboxheight");
-}
-
-/** tpm实训开启按钮,不允许多次点击 START */
-//点击模拟实战或者开启实战等,按钮变灰内容变成“开启中”
-var operationItem = null;
-var operationButtonOldValue = null;
-function opClickString(item) {
- var value = $(item).html();
- $(item).css({
- 'background': 'gray',
- 'border': '1px solid grey',
- 'pointer-events': 'none'
- });
- $(item).html('开启中');
-
- operationButtonOldValue = value
- operationItem = item
- // setTimeout(function(){ $(item).css('background', '#4CACFF');$(item).html(value); }, 4000)
-}
-//
-// var isOperationSending = false;
-$(document).bind('ajaxStop', function(event, xhr, settings) {
- if (settings && settings.url && (settings.url.match(/operation\?/))) {
- if (operationItem) {
- $(operationItem).css('background', '#4CACFF').css('pointer-events', 'inherit');
- $(operationItem).html(operationButtonOldValue);
- }
- }
-});
-$(document).bind('ajaxError', function(event, xhr, settings) {
- if (settings && settings.url && (settings.url.match(/operation\?/))) {
- if (operationItem) {
- $(operationItem).css('background', '#4CACFF').css('pointer-events', 'inherit');
- $(operationItem).html(operationButtonOldValue);
- }
- }
-});
-/** tpm实训开启按钮,不允许多次点击 END */
+document.write("");
+
+/*!
+ * JavaScript Cookie v2.2.0
+ * https://github.com/js-cookie/js-cookie
+ *
+ * Copyright 2006, 2015 Klaus Hartl & Fagner Brack
+ * Released under the MIT license
+ */
+!function(e) {
+ var n;
+ if ("function" == typeof define && define.amd && (define(e),
+ n = !0),
+ "object" == typeof exports && (module.exports = e(),
+ n = !0),
+ !n) {
+ var t = window.Cookies
+ , o = window.Cookies = e();
+ o.noConflict = function() {
+ return window.Cookies = t,
+ o
+ }
+ }
+}(function() {
+ function e() {
+ for (var e = 0, n = {}; e < arguments.length; e++) {
+ var t = arguments[e];
+ for (var o in t)
+ n[o] = t[o]
+ }
+ return n
+ }
+ function n(e) {
+ return e.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent)
+ }
+ return function t(o) {
+ function r() {}
+ function i(n, t, i) {
+ if ("undefined" != typeof document) {
+ "number" == typeof (i = e({
+ path: "/"
+ }, r.defaults, i)).expires && (i.expires = new Date(1 * new Date + 864e5 * i.expires)),
+ i.expires = i.expires ? i.expires.toUTCString() : "";
+ try {
+ var c = JSON.stringify(t);
+ /^[\{\[]/.test(c) && (t = c)
+ } catch (e) {}
+ t = o.write ? o.write(t, n) : encodeURIComponent(String(t)).replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent),
+ n = encodeURIComponent(String(n)).replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent).replace(/[\(\)]/g, escape);
+ var f = "";
+ for (var u in i)
+ i[u] && (f += "; " + u,
+ !0 !== i[u] && (f += "=" + i[u].split(";")[0]));
+ return document.cookie = n + "=" + t + f
+ }
+ }
+ function c(e, t) {
+ if ("undefined" != typeof document) {
+ for (var r = {}, i = document.cookie ? document.cookie.split("; ") : [], c = 0; c < i.length; c++) {
+ var f = i[c].split("=")
+ , u = f.slice(1).join("=");
+ t || '"' !== u.charAt(0) || (u = u.slice(1, -1));
+ try {
+ var a = n(f[0]);
+ if (u = (o.read || o)(u, a) || n(u),
+ t)
+ try {
+ u = JSON.parse(u)
+ } catch (e) {}
+ if (r[a] = u,
+ e === a)
+ break
+ } catch (e) {}
+ }
+ return e ? r[e] : r
+ }
+ }
+ return r.set = i,
+ r.get = function(e) {
+ return c(e, !1)
+ }
+ ,
+ r.getJSON = function(e) {
+ return c(e, !0)
+ }
+ ,
+ r.remove = function(n, t) {
+ i(n, "", e(t, {
+ expires: -1
+ }))
+ }
+ ,
+ r.defaults = {},
+ r.withConverter = t,
+ r
+ }(function() {})
+});
+
+$(function() {
+ var result = location.search.match(/\?search=(\w*)&?/i)
+ if (result && result[1]) {
+ var searchText = result[1]
+ $('#search-input').val(searchText)
+ }
+ // 未报名用户登录时弹框
+ // console.log(Cookies.get('enroll_status'));
+ // if(Cookies.get('enroll_status') == 0){
+ // Cookies.remove('enroll_status');
+ // var html=''+
+ // '
'+
+ // '
'+
+ // '
'+
+ // '
立即报名'+
+ // '
';
+ // $(".newContainer").append(html);
+ // }
+});
+
+function CloseBox() {
+ $(".CompetitionEnrollBox").remove();
+}
+
+//根据页面大小决定侧边栏的位置
+$(window).resize(function() {
+ rightSlider();
+});
+function rightSlider() {
+ var poi = parseInt((parseInt($(window).width()) - 1200) / 2) - 81;
+ // console.log(parseInt($(window).width())+" "+poi);
+ if (poi > 0) {
+ $(".-task-sidebar").css("right", poi);
+ } else {
+ $(".-task-sidebar").css("right", "0px");
+ }
+ $(".-task-sidebar").show();
+}
+function open_course(id, allowVisit) {
+ if (allowVisit) {
+ window.open("/courses/" + id);
+ }
+}
+function open_project(id, allowVisit) {
+ if (allowVisit) {
+ window.open("/projects/" + id);
+ }
+}
+
+function conver_size(limit) {
+ var size = "";
+ if (limit < 1024) {
+ //如果小于1KB转化成B
+ size = limit.toFixed(2) + "B";
+ } else if (limit < 1024 * 1024) {
+ //如果小于1MB转化成KB
+ size = (limit / 1024).toFixed(2) + "KB";
+ } else if (limit < 1024 * 1024 * 1024) {
+ //如果小于1GB转化成MB
+ size = (limit / (1024 * 1024)).toFixed(2) + "MB";
+ } else {
+ //其他转化成GB
+ size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB";
+ }
+
+ var sizestr = size + "";
+ var len = sizestr.indexOf("\.");
+ var dec = sizestr.substr(len + 1, 2);
+ if (dec == "00") {
+ //当小数点后为00时 去掉小数部分
+ return sizestr.substring(0, len) + sizestr.substr(len + 3, 2);
+ }
+ return sizestr;
+}
+
+function _initSider() {
+ var $descSide = $("").appendTo("body");
+ $(".-task-sidebar>div").hover(function() {
+ //移入显示二维码
+ if ($(this).hasClass("scan")) {
+ $(".scan_ewm").show().css({
+ right: "75px",
+ opacity: 0
+ }).stop().animate({
+ right: "45px",
+ opacity: 1
+ })
+ return;
+ }
+ var $tool = $(this).attr("tooltips");
+ $descSide.html($tool + "");
+ $descSide.data('_dom', this)
+ $descSide.show().css({
+ left: $(this).offset().left - $descSide.width() - 30,
+ opacity: 0,
+ top: $(this).offset().top
+ }).stop().animate({
+ left: $(this).offset().left - $descSide.width() - 5,
+ opacity: 1
+ }, 400);
+ }, function() {
+ if ($(this).hasClass("scan")) {
+ $(".scan_ewm").stop().animate({
+ right: "75px",
+ opacity: 0
+ }, 200).hide();
+ }
+ $descSide.stop().animate({
+ left: $(this).offset().left - $descSide.width() - 30,
+ opacity: 0
+ }, 200).hide();
+ });
+ rightSlider();
+
+ $(window).scroll(function() {
+ if ($descSide.height()) {
+ var hoverIcon = $descSide.data('_dom')
+ $descSide.css('top', $(hoverIcon).offset().top)
+ }
+ })
+}
+$(function() {
+ loadHeader();
+ _initSider();
+
+ $(window).scroll(function() {
+ if ($(".gotop").length > 0) {
+ if ($(document).scrollTop() > 0) {
+ $(".-task-sidebar .gotop").show();
+ $(".gotop").click(function() {
+ $("html,body").scrollTop(0);
+ });
+ }
+ if ($(document).scrollTop() == 0) {
+ $(".-task-sidebar .gotop").hide();
+ }
+ }
+ });
+
+ // 翻页的GO
+ $(".page_GO").live("keydown", function(event) {
+ var code;
+ if (!event) {
+ event = window.event;
+ //针对ie浏览器
+ code = event.keyCode;
+ } else {
+ code = event.keyCode;
+ }
+ if (code == 13) {
+ var prev = $(this).prev().find("a").html().trim();
+ var page = $(this).val().trim();
+ if (parseInt(prev) >= parseInt(page)) {
+ if (typeof ($(this).prev().children("a").attr("href")) == "undefined") {
+ var href = $(this).parent().find("li:first-child").children("a").attr("href");
+ } else {
+ var href = $(this).prev().children("a").attr("href");
+ }
+ var new_href = href.replace(/page=(\d*)/, 'page=' + page);
+ console.log(new_href);
+ $.get(new_href);
+ return false;
+ }
+ }
+ });
+
+ // 试用申请弹框
+ $("#apply_trail_submit_btn").live('click', function() {
+ if ($("#apply_reason").val().trim() == "") {
+ $("#hint_message").show();
+ } else {
+ $("#hint_message").hide();
+ $("#apply_trail_form").submit();
+ hideModal();
+ }
+ });
+
+});
+
+// editor 存在了jquery对象上,应用不需要自己写md_rec_data方法了
+function md_rec_data(k, mdu, id) {
+ if (window.sessionStorage.getItem(k + mdu) !== null) {
+ editor = $("#e_tips_" + id).data('editor');
+ editor.setValue(window.sessionStorage.getItem(k + mdu));
+
+ md_clear_data(k, mdu, id);
+ }
+}
+// markdown的自动保存
+function md_elocalStorage(editor, mdu, id) {
+ if (window.sessionStorage) {
+ var oc = window.sessionStorage.getItem('content' + mdu);
+ if (oc !== null) {
+ $("#e_tips_" + id).data('editor', editor);
+ var h = '您上次有已保存的数据,是否恢复 ? / 不恢复';
+ $("#e_tips_" + id).html(h);
+ }
+ setInterval(function() {
+ d = new Date();
+ var h = d.getHours();
+ var m = d.getMinutes();
+ var s = d.getSeconds();
+ h = h < 10 ? '0' + h : h;
+ m = m < 10 ? '0' + m : m;
+ s = s < 10 ? '0' + s : s;
+ if (editor.getValue().trim() != "") {
+ md_add_data("content", mdu, editor.getValue());
+ var id1 = "#e_tip_" + id;
+ var id2 = "#e_tips_" + id;
+ $(id1).html(" 数据已于 " + h + ':' + m + ':' + s + " 保存 ");
+ $(id2).html("");
+ }
+ }, 10000);
+
+ } else {
+ $("#e_tip_" + id).after('您的浏览器不支持localStorage.无法开启自动保存草稿服务,请升级浏览器!');
+ }
+}
+// 保存数据
+function md_add_data(k, mdu, d) {
+ window.sessionStorage.setItem(k + mdu, d);
+}
+// 恢复数据
+//function md_rec_data(k,mdu,id, editor){
+// if(window.sessionStorage.getItem(k+mdu) !== null){
+// editor.setValue(window.sessionStorage.getItem(k+mdu));
+// md_clear_data(k,mdu,id);
+// }
+//}
+// 清空保存的数据
+function md_clear_data(k, mdu, id) {
+ window.sessionStorage.removeItem(k + mdu);
+ var id1 = "#e_tip_" + id;
+ var id2 = "#e_tips_" + id;
+ if (k == 'content') {
+ $(id2).html("");
+ } else {
+ $(id1).html("");
+ }
+}
+
+// editorMD to create
+/**
+ *
+ * @param id 渲染DOM的id
+ * @param width 宽度
+ * @param high 高度
+ * @param placeholder
+ * @param imageUrl 上传图片的url
+ * @returns {*} 返回一个editorMD实例
+ */
+function create_editorMD(id, width, high, placeholder, imageUrl, readonly) {
+ var readonly = readonly == undefined ? false : readonly;
+ var editorName = editormd(id, {
+ width: width,
+ height: high,
+ syncScrolling: "single",
+ //你的lib目录的路径,我这边用JSP做测试的
+ path: "/editormd/lib/",
+ tex: true,
+ tocm: true,
+ emoji: true,
+ taskList: true,
+ codeFold: true,
+ searchReplace: true,
+ htmlDecode: "style,script,iframe",
+ sequenceDiagram: true,
+ autoFocus: false,
+ readonly: readonly,
+ toolbarIcons: function() {
+ // Or return editormd.toolbarModes[name]; // full, simple, mini
+ // Using "||" set icons align right.
+ return ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "|", "testIcon", "testIcon1", '|', "image", "table", '|', "watch", "clear"]
+ },
+ toolbarCustomIcons: {
+ testIcon: "",
+ testIcon1: ""
+ },
+ //这个配置在simple.html中并没有,但是为了能够提交表单,使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中,方便post提交表单。
+ saveHTMLToTextarea: true,
+ // 用于增加自定义工具栏的功能,可以直接插入HTML标签,不使用默认的元素创建图标
+ dialogMaskOpacity: 0.6,
+ placeholder: placeholder,
+ imageUpload: true,
+ imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
+ imageUploadURL: imageUrl,
+ //url
+ onload: function(cMirror) {
+ $("#" + id + " [type=\"latex\"]").bind("click", function() {
+ editorName.cm.replaceSelection("```latex");
+ editorName.cm.replaceSelection("\n");
+ editorName.cm.replaceSelection("\n");
+ editorName.cm.replaceSelection("```");
+ var __Cursor = editorName.cm.getDoc().getCursor();
+ editorName.cm.setCursor(__Cursor.line - 1, 0);
+ });
+
+ $("#" + id + " [type=\"inline\"]").bind("click", function() {
+ editorName.cm.replaceSelection("$$$$");
+ var __Cursor = editorName.cm.getDoc().getCursor();
+ editorName.cm.setCursor(__Cursor.line, __Cursor.ch - 2);
+ editorName.cm.focus();
+ });
+ $("[type=\"inline\"]").attr("title", "行内公式");
+ $("[type=\"latex\"]").attr("title", "多行公式");
+ setTimeout(function() {
+ editorName.resize();
+ editorName.cm.refresh();
+ window.new_md = editorName;
+ }, 300);
+ }
+ });
+ return editorName;
+}
+
+// editormd to html
+/**
+ *
+ * @param id 渲染的id
+ * @param callback onload回調 暫時未用
+ */
+function editormd_to_html(id, callback) {
+ editormd.loadKaTeX(function() {
+ editormd.markdownToHTML(id, {
+ htmlDecode: "style,script,iframe",
+ // you can filter tags decode
+ onload: function() {
+ callback && callback()
+ },
+ taskList: true,
+ tex: true,
+ // 默认不解析
+ flowChart: true,
+ // 默认不解析
+ sequenceDiagram: true// 默认不解析
+ });
+ });
+}
+
+function loadHeader() {
+ //头部导航条的----------显示搜索框
+ $("#search-open").on("click", function(e) {
+ $(this).hide();
+ // $("#header-nav").animate({opacity:"0"},1000);
+ $(".posi-search").show()
+ // .animate({opacity:"1"},1000);
+ $("#header-nav").css("z-index", "2");
+ $(".posi-search").css("z-index", "3");
+ // $(".search-input").val(""); // 不清空
+ $(".search-input").focus();
+ $(".search-all .search-content").hide();
+ e.stopPropagation();
+ //阻止冒泡
+ });
+ $(".search-input").on("click", function(e) {
+ e.stopPropagation();
+ //阻止冒泡
+ });
+ //搜索框输入内容
+ $(".search-input").on("input", function(e) {
+ if ($(".search-input").val() == "") {
+ $(".search-all .search-content").hide();
+ } else {
+ $(".search-all .search-content").show();
+ }
+ e.stopPropagation();
+ //阻止冒泡
+ });
+ //搜索
+ $("#header_keyword_search").on("click", header_search);
+ $("input[name='search_keyword']").on("keydown", function(event) {
+ var code;
+ if (!event) {
+ event = window.event;
+ //针对ie浏览器
+ code = event.keyCode;
+ } else {
+ code = event.keyCode;
+ }
+ if (code == 13) {
+ header_search();
+ return false;
+ }
+ });
+ $(".search-clear").click(function(e) {
+ e.stopPropagation();
+ });
+ //切换搜索条件
+ $("#searchkey li").click(function(e) {
+ var key = $($(this).children("a")[0]).html();
+ switch (key) {
+ case '实训':
+ $("#search_type").val('1');
+ break;
+ case '课堂':
+ $("#search_type").val('2');
+ break;
+ case '用户':
+ $("#search_type").val('3');
+ break;
+ }
+ $("#searchkey").siblings(".searchkey").html(key);
+ // $("#searchkey").hide();
+ e.stopPropagation();
+ //阻止冒泡
+ });
+ //切换选择导航条
+ $("#header-nav li").click(function() {
+ $("#header-nav li").removeClass("active");
+ $(this).addClass("active");
+ });
+ //点击页面其它(与搜索框无关的地方)都会将搜索框隐藏,所以与搜索框有关的地方需要阻止冒泡
+ $("body").on("click", function() {
+ closeSearch();
+ });
+
+ $(".search_history").on("click", function() {
+ $("input[name='search_keyword']").val($(this).html());
+ header_search();
+ });
+}
+
+function header_search() {
+ var keyword = $("input[name='search_keyword']").val();
+ // 搜索关键字
+ var index = $("#search_type").val();
+ // 搜索课程/项目
+ keyword = encodeURIComponent(keyword);
+ // $.get('/users/search_shixuns_or_course',
+ // { search: keyword,
+ // index: index});
+ window.location.href = "/users/search_shixuns_or_courses" + "?search=" + keyword + "&index=" + index;
+ //e.stopPropagation();//阻止冒泡
+}
+
+//头部导航条的隐藏
+function closeSearch() {
+ $('#posi-search').hide();
+ $("#search-open").show();
+ // $(".posi-search").animate({opacity:"0"},800);
+ $("#header-nav").animate({
+ opacity: "1"
+ }, 1000);
+ $(".posi-search").css("z-index", "2");
+ $("#header-nav").css("z-index", "3");
+}
+(function($) {
+ $.fn.drag = function(options) {
+ var x, drag = this, isMove = false, defaults = {};
+ var options = $.extend(defaults, options);
+ //添加背景,文字,滑块
+ var html = '' + '请拖住滑块,拖动到最右边
' + '';
+ this.append(html);
+
+ var handler = drag.find('.handler');
+ var drag_bg = drag.find('.drag_bg');
+ var text = drag.find('.drag_text');
+ var maxWidth = text.width() - handler.width();
+ //能滑动的最大间距
+ //鼠标按下时候的x轴的位置
+ handler.mousedown(function(e) {
+ isMove = true;
+ x = e.pageX - parseInt(handler.css('left'), 10);
+ });
+
+ //鼠标指针在上下文移动时,移动距离大于0小于最大间距,滑块x轴位置等于鼠标移动距离
+ $(document).mousemove(function(e) {
+ var _x = e.pageX - x;
+ var handler_offset = handler.offset();
+ var lastX = e.clientX - x;
+ lastX = Math.max(0, Math.min(maxWidth, lastX));
+ if (isMove) {
+ if (_x > 0 && _x <= maxWidth) {
+ handler.css({
+ 'left': lastX
+ });
+ drag_bg.css({
+ 'width': lastX
+ });
+ } else if (lastX > maxWidth - 5 && lastX < maxWidth + 5) {
+ //鼠标指针移动距离达到最大时清空事件
+ dragOk();
+ }
+ }
+ });
+ handler.mouseup(function(e) {
+ isMove = false;
+ var _x = e.pageX - x;
+ if (_x < maxWidth) {
+ //鼠标松开时,如果没有达到最大距离位置,滑块就返回初始位置
+ handler.css({
+ 'left': 0
+ });
+ drag_bg.css({
+ 'width': 0
+ });
+ }
+ });
+
+ //清空事件
+ function dragOk() {
+ var kuaiwidth = drag.width() - handler.width();
+ handler.removeClass('handler_bg').addClass('handler_ok_bg');
+ handler.css({
+ 'left': kuaiwidth + 'px'
+ })
+ text.css({
+ 'width': kuaiwidth + 'px'
+ });
+ text.text('验证通过');
+ drag.css({
+ 'color': '#fff'
+ });
+ drag_bg.css({
+ 'width': kuaiwidth + 'px'
+ })
+ handler.unbind('mousedown');
+ $(document).unbind('mousemove');
+ $(document).unbind('mouseup');
+ handler.parent().next().find("p").html("").hide();
+ }
+ }
+ ;
+}
+)(jQuery);
+
+//判断是手机端还是电脑端
+function IsPC() {
+ var userAgentInfo = navigator.userAgent;
+ var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
+ var flag = true;
+ for (var v = 0; v < Agents.length; v++) {
+ if (userAgentInfo.indexOf(Agents[v]) > 0) {
+ flag = false;
+ break;
+ }
+ }
+ return flag;
+}
+
+//Dom:绑定事件的节点对象,ChangeDOM:操作的相关节点,
+function LeaveTitle(Dom, ChangeDom) {
+ ChangeDom.html("").hide();
+ ChangeDom.parent().css({
+ opacity: 0,
+ left: 0,
+ top: 0
+ }).hide();
+}
+
+$(function() {
+ //平台tip的样式优化js
+ var $desc = $("" + "
" + "
" + "
" + "
" + "
").appendTo("body");
+ //Dom:绑定事件的节点对象,ChangeDOM:操作的相关节点,
+ function LeaveTitle(Dom, ChangeDom) {
+ Dom.live("mouseleave", function() {
+ ChangeDom.html("").hide();
+ $desc.css({
+ opacity: 0,
+ left: 0,
+ top: 0
+ }).hide();
+ })
+ }
+ LeaveTitle($("[data-tip-top]"), $(".data-tip-top"));
+ LeaveTitle($("[data-tip-down]"), $(".data-tip-down"));
+ LeaveTitle($("[data-tip-right]"), $(".data-tip-left"));
+ LeaveTitle($("[data-tip-left]"), $(".data-tip-right"));
+ $("[data-tip-top]").live("mouseenter", function() {
+ var $tool = $(this).attr("data-tip-top");
+ if ($tool != "") {
+ $(".data-tip-top").show().html($tool);
+ $desc.show().css({
+ left: $(this).offset().left - ($desc.width() - $(this).outerWidth()) / 2,
+ opacity: 1,
+ top: $(this).offset().top - 30
+ });
+ }
+ });
+ $("[data-tip-down]").live("mouseenter", function() {
+ var $tool = $(this).attr("data-tip-down");
+ if ($tool != "") {
+ $(".data-tip-down").show().html($tool);
+ $desc.show().css({
+ left: $(this).offset().left - ($desc.width() - $(this).outerWidth()) / 2,
+ opacity: 1,
+ top: $(this).offset().top + $(this).height() + 6
+ });
+ }
+ });
+ $("[data-tip-right]").live("mouseenter", function() {
+ var $tool = $(this).attr("data-tip-right");
+ if ($tool != "") {
+ console.log($(this).offset().left + " " + $(this).width());
+ $(".data-tip-left").show().html($tool);
+ $desc.show().css({
+ left: $(this).offset().left + $(this).outerWidth() + 6,
+ opacity: 1,
+ top: $(this).offset().top - ($desc.height() - $(this).height()) / 2
+ });
+ }
+ });
+ $("[data-tip-left]").live("mouseenter", function() {
+ var $tool = $(this).attr("data-tip-left");
+ if ($tool != "") {
+ $(".data-tip-right").show().html($tool);
+ $desc.show().css({
+ left: $(this).offset().left - $desc.width() - 6,
+ opacity: 1,
+ top: $(this).offset().top - ($desc.height() - $(this).height()) / 2
+ });
+ }
+ });
+ unitDownOption();
+});
+
+function unitDownOption() {
+ //下拉框
+ $("[select-for]").append("");
+ $("[select-for]").hover(function() {
+ $(this).find(".down-select").show();
+ }, function() {
+ $(this).find(".down-select").hide();
+ })
+ $("[select-for] .down-select p").bind("click", function() {
+ //alert($(this).attr("data-shixun-value"));
+ if ($(this).attr("id") == "diy_script") {
+ return;
+ // 实训新建-选择自定义脚本diy
+ }
+ $(this).parents(".down-select").siblings("input[type=hidden]").attr("value", $(this).attr("data-shixun-value"));
+
+ $(this).parents(".down-select").siblings("input[type=text]").val($(this).html().trim());
+ $(this).parents(".down-select").hide();
+ })
+}
+
+//初始化省份
+function showprovince(id) {
+ var arrary = ["北京", "上海", "广东", "江苏", "浙江", "重庆", "安徽", "福建", "甘肃", "广西", "贵州", "海南", "河北", "黑龙江", "河南", "湖北", "湖南", "江西", "吉林", "辽宁", "内蒙古", "宁夏", "青海", "山东", "山西", "陕西", "四川", "天津", "新疆", "西藏", "云南", "香港特别行政区", "澳门特别行政区", "台湾", "海外"];
+ var html = ""
+ for (var i = 0; i < arrary.length; i++) {
+ var item = arrary[i];
+ html += "";
+ }
+ $("#" + id).html(html);
+}
+
+//省市下拉框
+function showcity(province, cityField) {
+ switch (province) {
+ case "北京":
+ var cityOptions = new Array("东城","西城","朝阳","丰台","石景山","海淀","门头沟","房山","通州","顺义","昌平","大兴","平谷","怀柔","密云","延庆");
+ break;
+ case "上海":
+ var cityOptions = new Array("崇明","黄浦","卢湾","徐汇","长宁","静安","普陀","闸北","虹口","杨浦","闵行","宝山","嘉定","浦东","金山","松江","青浦","南汇","奉贤");
+ break;
+ case "广东":
+ var cityOptions = new Array("广州","深圳","珠海","东莞","中山","佛山","惠州","河源","潮州","江门","揭阳","茂名","梅州","清远","汕头","汕尾","韶关","顺德","阳江","云浮","湛江","肇庆");
+ break;
+ case "江苏":
+ var cityOptions = new Array("南京","常熟","常州","海门","淮安","江都","江阴","昆山","连云港","南通","启东","沭阳","宿迁","苏州","太仓","泰州","同里","无锡","徐州","盐城","扬州","宜兴","仪征","张家港","镇江","周庄");
+ break;
+ case "重庆":
+ var cityOptions = new Array("万州","涪陵","渝中","大渡口","江北","沙坪坝","九龙坡","南岸","北碚","万盛","双挢","渝北","巴南","黔江","长寿","綦江","潼南","铜梁","大足","荣昌","壁山","梁平","城口","丰都","垫江","武隆","忠县","开县","云阳","奉节","巫山","巫溪","石柱","秀山","酉阳","彭水","江津","合川","永川","南川");
+ break;
+ case "安徽":
+ var cityOptions = new Array("合肥","安庆","蚌埠","亳州","巢湖","滁州","阜阳","贵池","淮北","淮化","淮南","黄山","九华山","六安","马鞍山","宿州","铜陵","屯溪","芜湖","宣城");
+ break;
+ case "福建":
+ var cityOptions = new Array("福州","厦门","泉州","漳州","龙岩","南平","宁德","莆田","三明");
+ break;
+ case "甘肃":
+ var cityOptions = new Array("兰州","白银","定西","敦煌","甘南","金昌","酒泉","临夏","平凉","天水","武都","武威","西峰","张掖");
+ break;
+ case "广西":
+ var cityOptions = new Array("南宁","百色","北海","桂林","防城港","贵港","河池","贺州","柳州","钦州","梧州","玉林");
+ break;
+ case "贵州":
+ var cityOptions = new Array("贵阳","安顺","毕节","都匀","凯里","六盘水","铜仁","兴义","玉屏","遵义");
+ break;
+ case "海南":
+ var cityOptions = new Array("海口","儋县","陵水","琼海","三亚","通什","万宁");
+ break;
+ case "河北":
+ var cityOptions = new Array("石家庄","保定","北戴河","沧州","承德","丰润","邯郸","衡水","廊坊","南戴河","秦皇岛","唐山","新城","邢台","张家口");
+ break;
+ case "黑龙江":
+ var cityOptions = new Array("哈尔滨","北安","大庆","大兴安岭","鹤岗","黑河","佳木斯","鸡西","牡丹江","齐齐哈尔","七台河","双鸭山","绥化","伊春");
+ break;
+ case "河南":
+ var cityOptions = new Array("郑州","安阳","鹤壁","潢川","焦作","济源","开封","漯河","洛阳","南阳","平顶山","濮阳","三门峡","商丘","新乡","信阳","许昌","周口","驻马店");
+ break;
+ case "香港":
+ var cityOptions = new Array("香港","九龙","新界");
+ break;
+ case "湖北":
+ var cityOptions = new Array("武汉","恩施","鄂州","黄冈","黄石","荆门","荆州","潜江","十堰","随州","武穴","仙桃","咸宁","襄阳","襄樊","孝感","宜昌");
+ break;
+ case "湖南":
+ var cityOptions = new Array("长沙","常德","郴州","衡阳","怀化","吉首","娄底","邵阳","湘潭","益阳","岳阳","永州","张家界","株洲");
+ break;
+ case "江西":
+ var cityOptions = new Array("南昌","抚州","赣州","吉安","景德镇","井冈山","九江","庐山","萍乡","上饶","新余","宜春","鹰潭");
+ break;
+ case "吉林":
+ var cityOptions = new Array("长春","吉林","白城","白山","珲春","辽源","梅河","四平","松原","通化","延吉");
+ break;
+ case "辽宁":
+ var cityOptions = new Array("沈阳","鞍山","本溪","朝阳","大连","丹东","抚顺","阜新","葫芦岛","锦州","辽阳","盘锦","铁岭","营口");
+ break;
+ case "澳门":
+ var cityOptions = new Array("澳门");
+ break;
+ case "内蒙古":
+ var cityOptions = new Array("呼和浩特","阿拉善盟","包头","赤峰","东胜","海拉尔","集宁","临河","通辽","乌海","乌兰浩特","锡林浩特");
+ break;
+ case "宁夏":
+ var cityOptions = new Array("银川","固源","石嘴山","吴忠");
+ break;
+ case "青海":
+ var cityOptions = new Array("西宁","德令哈","格尔木","共和","海东","海晏","玛沁","同仁","玉树");
+ break;
+ case "山东":
+ var cityOptions = new Array("济南","滨州","兖州","德州","东营","菏泽","济宁","莱芜","聊城","临沂","蓬莱","青岛","曲阜","日照","泰安","潍坊","威海","烟台","枣庄","淄博");
+ break;
+ case "山西":
+ var cityOptions = new Array("太原","长治","大同","候马","晋城","离石","临汾","宁武","朔州","忻州","阳泉","榆次","运城");
+ break;
+ case "陕西":
+ var cityOptions = new Array("西安","安康","宝鸡","汉中","渭南","商州","绥德","铜川","咸阳","延安","榆林");
+ break;
+ case "四川":
+ var cityOptions = new Array("成都","巴中","达川","德阳","都江堰","峨眉山","涪陵","广安","广元","九寨沟","康定","乐山","泸州","马尔康","绵阳","眉山","南充","内江","攀枝花","遂宁","汶川","西昌","雅安","宜宾","自贡","资阳");
+ break;
+ case "台湾":
+ var cityOptions = new Array("台北","基隆","台南","台中","高雄","屏东","南投","云林","新竹","彰化","苗栗","嘉义","花莲","桃园","宜兰","台东","金门","马祖","澎湖");
+ break;
+ case "天津":
+ var cityOptions = new Array("天津","和平","东丽","河东","西青","河西","津南","南开","北辰","河北","武清","红挢","塘沽","汉沽","大港","宁河","静海","宝坻","蓟县");
+ break;
+ case "新疆":
+ var cityOptions = new Array("乌鲁木齐","阿克苏","阿勒泰","阿图什","博乐","昌吉","东山","哈密","和田","喀什","克拉玛依","库车","库尔勒","奎屯","石河子","塔城","吐鲁番","伊宁");
+ break;
+ case "西藏":
+ var cityOptions = new Array("拉萨","阿里","昌都","林芝","那曲","日喀则","山南");
+ break;
+ case "云南":
+ var cityOptions = new Array("昆明","大理","保山","楚雄","大理","东川","个旧","景洪","开远","临沧","丽江","六库","潞西","曲靖","思茅","文山","西双版纳","玉溪","中甸","昭通");
+ break;
+ case "浙江":
+ var cityOptions = new Array("杭州","安吉","慈溪","定海","奉化","海盐","黄岩","湖州","嘉兴","金华","临安","临海","丽水","宁波","瓯海","平湖","千岛湖","衢州","江山","瑞安","绍兴","嵊州","台州","温岭","温州","余姚","舟山");
+ break;
+ case "海外":
+ var cityOptions = new Array("美国","日本","英国","法国","德国","其他");
+ break;
+ default:
+ var cityOptions = new Array("请选择所在城市");
+ break;
+ }
+
+ cityField.options.length = 0;
+ for (var i = 0; i < cityOptions.length; i++) {
+ cityField.options[i] = new Option(cityOptions[i],cityOptions[i]);
+ /*
+ if (cityField.options[i].value==city)
+ {
+ //alert("here put City ok!");
+ document.oblogform["city"].selectedIndex = i;
+ }*/
+ }
+}
+
+/*弹框*/
+// 公共弹框样式
+// 建议左右栏的:Width:460,Height:190
+// 建议宽屏对应值:Width:760,Height:500
+function pop_box_new(value, Width, Height, close) {
+
+ if ($("#popupAll").length > 0) {
+ $("#popupAll").remove();
+ }
+ w = ($(window).width() - Width) / 2;
+ h = ($(window).height() - Height) / 2;
+ var html = "";
+ if (close) {
+ value = "" + value;
+ }
+ $(document.body).append(html);
+ $("#popupWrap").html(value);
+ $('#popupWrap').css({
+ "top": h + "px",
+ "left": w + "px",
+ "padding": "0",
+ "border": "none",
+ "position": "fixed",
+ "z-index": "99999",
+ "background-color": "#fff",
+ "border-radius": "10px"
+ });
+ if (close) {
+ $('#closeIcon').css({
+ "top": "-26px",
+ "left": Width + "px",
+ "z-index": "100000"
+ });
+ }
+
+ $("#popupWrap").parent().parent().show();
+ $('#popupAll').find("#closeIcon").click(function() {
+ $("#popupAll").hide();
+ });
+ $('#popupAll').find("a[class*='pop_close']").click(function() {
+ $("#popupAll").hide();
+ });
+ // w = ($(window).width() - Width)/2;
+ // h = ($(window).height() - Height)/2;
+ // $("#ajax-modal").html(value);
+ // showModal('ajax-modal', Width + 'px');
+ // $('#ajax-modal').siblings().remove();
+ // $('#ajax-modal').parent().css({"top": h+"px","left": w+"px","padding":"0","border":"none","position":"fixed"});
+ // $('#ajax-modal').parent().removeClass("resourceUploadPopup popbox_polls popbox");
+ // $('#ajax-modal').css({"padding":"0","overflow":"hidden"});
+ // $('#ajax-modal').parent().attr("id","popupWrap");
+
+ //拖拽
+ function Drag(id) {
+ this.div = document.getElementById(id);
+ if (this.div) {
+ this.div.style.cursor = "move";
+ this.div.style.position = "fixed";
+ }
+ this.disX = 0;
+ this.disY = 0;
+ var _this = this;
+ this.div.onmousedown = function(evt) {
+ _this.getDistance(evt);
+ document.onmousemove = function(evt) {
+ _this.setPosition(evt);
+ }
+ ;
+ _this.div.onmouseup = function() {
+ _this.clearEvent();
+ }
+ }
+ }
+ Drag.prototype.getDistance = function(evt) {
+ var oEvent = evt || event;
+ this.disX = oEvent.clientX - this.div.offsetLeft;
+ this.disY = oEvent.clientY - this.div.offsetTop;
+ }
+ ;
+ Drag.prototype.setPosition = function(evt) {
+ var oEvent = evt || event;
+ var l = oEvent.clientX - this.disX;
+ var t = oEvent.clientY - this.disY;
+ if (l <= 0) {
+ l = 0;
+ } else if (l >= document.documentElement.clientWidth - this.div.offsetWidth) {
+ l = document.documentElement.clientWidth - this.div.offsetWidth;
+ }
+ if (t <= 0) {
+ t = 0;
+ } else if (t >= document.documentElement.clientHeight - this.div.offsetHeight) {
+ t = document.documentElement.clientHeight - this.div.offsetHeight;
+ }
+ this.div.style.left = l + "px";
+ this.div.style.top = t + "px";
+ }
+ ;
+ Drag.prototype.clearEvent = function() {
+ this.div.onmouseup = null;
+ document.onmousemove = null;
+ }
+ ;
+
+ new Drag("popupWrap");
+
+ $("#popupAll input, #popupAll textarea, #popupAll select, #popupAll ul, #popupAll a,#shixun_search_form_div").mousedown(function(event) {
+ event.stopPropagation();
+ new Drag("popupWrap");
+ });
+
+}
+
+function hideModal(el) {
+ if ($("#popupAll").length > 0) {
+ $("#popupAll").remove();
+ } else {
+ var modal;
+ if (el) {
+ modal = $(el).parents('.ui-dialog-content');
+ } else {
+ modal = $('#ajax-modal');
+ }
+ modal.dialog("close");
+ }
+}
+
+//提示框:只有一个确定按钮,点击跳转
+//
+function notice_box_redirect(url, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 480, 160);
+}
+//按钮内容自定义(自定义按钮需要remote=true,且有取消按钮)
+function notice_operation_box(url, str, btnstr) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 480, 160);
+}
+//点击删除时的确认弹框: 不走destroy方法
+function delete_confirm_box(url, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 480, 160);
+}
+//点击删除时的确认弹框: 走destroy方法,remote为true
+function delete_confirm_box_2(url, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 480, 160);
+}
+
+// 点击确定的时候ajax请求,两个按钮 点击确认跳转, 提示信息可以多行
+function op_confirm_box_remote(url, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 578, 205);
+}
+
+//点击删除时的确认弹框: post,remote为true
+function post_confirm_box(url, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 480, 160);
+}
+
+//提示框:只有一个确定按钮,点击关闭弹框
+//
+function notice_box(str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 480, 160);
+}
+
+//点击删除时的确认弹框: 走destroy方法
+function delete_confirm_box_3(url, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 480, 160);
+}
+
+//取消和确定,确定会调用自定义方法
+function op_confirm_tip(str, func) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 500, 205);
+}
+
+//取消和确定,确定会调用自定义方法(带参数)
+function op_confirm_tip_1(str, func) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 500, 205);
+}
+
+function op_confirm_box_loading(url, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 578, 205);
+}
+
+// 两个按钮 点击确认跳转, 提示信息有两行
+function s_op_confirm_box(url, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, 480, 205);
+}
+
+function suofang() {
+ var html = '可能会影响某些功能的正常使用
' + '- 1.请尝试调整浏览器缩放比例为100%(快捷键ctrl+0)
' + '- 2.请尝试调整系统显示比例为100%(控制面板/显示 设置)
' + '
';
+ sure_confirm_box("页面缩放比例不正确", 600, 310, html);
+}
+
+//一个“知道了”按钮,title和宽度都作为参数
+function sure_confirm_box(title, width, height, str) {
+ var htmlvalue = '';
+ pop_box_new(htmlvalue, width, height);
+}
+
+function throttle(method, context, e) {
+ clearTimeout(method.tId);
+ method.tId = setTimeout(function() {
+ method.call(context, e);
+ }, 500);
+}
+
+function apply_publish_shixun(url) {
+ if ($("#apply_publish_shixun").attr("data-option") == '1') {
+ $("#apply_publish_shixun").attr("data-option", 0);
+ $("#apply_publish_shixun").addClass("disabled-grey-bg");
+ $.ajax({
+ url: url,
+ type: 'get'
+ });
+ }
+}
+
+var autoTextarea = function(elem, extra, maxHeight) {
+ extra = extra || 0;
+ var isFirefox = !!document.getBoxObjectFor || 'mozInnerScreenX'in window
+ , isOpera = !!window.opera && !!window.opera.toString().indexOf('Opera')
+ , addEvent = function(type, callback) {
+ elem.addEventListener ? elem.addEventListener(type, callback, false) : elem.attachEvent('on' + type, callback);
+ }
+ , getStyle = elem.currentStyle ? function(name) {
+ var val = elem.currentStyle[name];
+
+ if (name === 'height' && val.search(/px/i) !== 1) {
+ var rect = elem.getBoundingClientRect();
+ return rect.bottom - rect.top - parseFloat(getStyle('paddingTop')) - parseFloat(getStyle('paddingBottom')) + 'px';
+ }
+ ;
+ return val;
+ }
+ : function(name) {
+ return getComputedStyle(elem, null)[name];
+ }
+ , minHeight = parseFloat(getStyle('height'));
+
+ elem.style.resize = 'none';
+
+ var change = function() {
+ var scrollTop, height, padding = 0, style = elem.style;
+
+ if (elem._length === elem.value.length)
+ return;
+ elem._length = elem.value.length;
+
+ if (!isFirefox && !isOpera) {
+ padding = parseInt(getStyle('paddingTop')) + parseInt(getStyle('paddingBottom'));
+ }
+ ;scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
+
+ elem.style.height = minHeight + 'px';
+ if (elem.scrollHeight > minHeight) {
+ if (maxHeight && elem.scrollHeight > maxHeight) {
+ height = maxHeight - padding;
+ style.overflowY = 'auto';
+ } else {
+ height = elem.scrollHeight - padding + 10;
+ style.overflowY = 'hidden';
+ }
+ ;style.height = height + extra + 'px';
+ scrollTop += parseInt(style.height) - elem.currHeight;
+ //document.body.scrollTop = scrollTop;
+ //document.documentElement.scrollTop = scrollTop;
+ elem.currHeight = parseInt(style.height);
+ }
+ ;
+ };
+
+ addEvent('propertychange', change);
+ addEvent('input', change);
+ addEvent('focus', change);
+ change();
+};
+
+// 点击按钮复制功能
+function jsCopy() {
+ var e = document.getElementById("copy_rep_content");
+ e.select();
+ document.execCommand("Copy");
+}
+
+// 使用resize事件监听窗口的zoom,如果zoom变化了,弹框提示;初始化时也检查zoom是否是100%。
+function _initZoomCheck() {
+ if (!IsPC()) {
+ // 手机端不需要提示
+ return;
+ }
+ var isNormalZoom = Math.round(window.devicePixelRatio * 100) === 100
+ if (!isNormalZoom) {
+ suofang();
+ }
+
+ $(window).resize(function() {
+ var isNormalZoom = Math.round(window.devicePixelRatio * 100) === 100
+ if (!isNormalZoom) {
+ suofang();
+ } else {
+ $('.task-btn.task-btn-orange:visible').click()
+ }
+ })
+
+}
+
+var win_resize = function() {
+ var _w = $(window).width() - 1200;
+ if (_w < 0) {
+ $('.newHeader>.educontent').width('auto')
+ } else {
+ $('.newHeader>.educontent').width('1200px')
+ }
+};
+
+function initWindowResize() {
+ if (location.pathname === '/login') {
+ // 登录页不需要
+ return;
+ }
+
+ $(function() {
+ setTimeout(function() {
+ win_resize();
+ }, 1000)
+ })
+
+ $(window).resize(function() {
+ win_resize()
+ })
+}
+initWindowResize();
+
+// 登录刷新 https://stackoverflow.com/questions/28230845/communication-between-tabs-or-windows
+if (window['BroadcastChannel']) {
+ var bc = new BroadcastChannel('ec_reload');
+ bc.onmessage = function(ev) {
+ if (window['ec_reload_msg_send_window']) {
+ window['ec_reload_msg_send_window'] = false;
+ } else {
+ location.reload();
+ }
+ }
+}
+function _sendReloadMsg() {
+ var bc = new BroadcastChannel('ec_reload');
+ window['ec_reload_msg_send_window'] = true;
+ // 消息发出的窗口,不需要处理该消息
+ bc.postMessage('ec_reload');
+ /* send */
+}
+// IE11 没有 startsWith
+if (!String.prototype.startsWith) {
+ String.prototype.startsWith = function(searchString, position) {
+ position = position || 0;
+ return this.substr(position, searchString.length) === searchString;
+ }
+ ;
+ String.prototype.endsWith = function(search, this_len) {
+ if (this_len === undefined || this_len > this.length) {
+ this_len = this.length;
+ }
+ return this.substring(this_len - search.length, this_len) === search;
+ }
+ ;
+}
+
+function clickNewsubscript() {
+ $(".newsubscript").hide();
+ $(".newedbox").addClass("newminheight");
+ $(".newedbox").removeClass("newedboxheight");
+}
+
+/** tpm实训开启按钮,不允许多次点击 START */
+//点击模拟实战或者开启实战等,按钮变灰内容变成“开启中”
+var operationItem = null;
+var operationButtonOldValue = null;
+function opClickString(item) {
+ var value = $(item).html();
+ $(item).css({
+ 'background': 'gray',
+ 'border': '1px solid grey',
+ 'pointer-events': 'none'
+ });
+ $(item).html('开启中');
+
+ operationButtonOldValue = value
+ operationItem = item
+ // setTimeout(function(){ $(item).css('background', '#4CACFF');$(item).html(value); }, 4000)
+}
+//
+// var isOperationSending = false;
+$(document).bind('ajaxStop', function(event, xhr, settings) {
+ if (settings && settings.url && (settings.url.match(/operation\?/))) {
+ if (operationItem) {
+ $(operationItem).css('background', '#4CACFF').css('pointer-events', 'inherit');
+ $(operationItem).html(operationButtonOldValue);
+ }
+ }
+});
+$(document).bind('ajaxError', function(event, xhr, settings) {
+ if (settings && settings.url && (settings.url.match(/operation\?/))) {
+ if (operationItem) {
+ $(operationItem).css('background', '#4CACFF').css('pointer-events', 'inherit');
+ $(operationItem).html(operationButtonOldValue);
+ }
+ }
+});
+/** tpm实训开启按钮,不允许多次点击 END */
diff --git a/public/react/config/webpack.config.dev.js b/public/react/config/webpack.config.dev.js
index ceddbd81b..12d5e437f 100644
--- a/public/react/config/webpack.config.dev.js
+++ b/public/react/config/webpack.config.dev.js
@@ -9,7 +9,7 @@ const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
-// const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
+const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const getClientEnvironment = require('./env');
const paths = require('./paths');
@@ -28,8 +28,8 @@ const env = getClientEnvironment(publicUrl);
// The production configuration is different and lives in a separate file.
module.exports = {
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
- // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
- devtool: 'cheap-module-source-map',
+ // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s
+ devtool: "eval", // 开启调试
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
@@ -113,7 +113,6 @@ module.exports = {
// First, run the linter.
// It's important to do this before Babel processes the JS.
- // 上线然后要注释回来
// {
// test: /\.(js|jsx|mjs)$/,
// enforce: 'pre',
@@ -250,7 +249,8 @@ module.exports = {
// 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$/),
- ],
+ new MonacoWebpackPlugin(),
+ ],
// 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: {
diff --git a/public/react/config/webpack.config.prod.js b/public/react/config/webpack.config.prod.js
index 92feea0a4..0abd707af 100644
--- a/public/react/config/webpack.config.prod.js
+++ b/public/react/config/webpack.config.prod.js
@@ -1,349 +1,365 @@
-'use strict';
-
-const autoprefixer = require('autoprefixer');
-const path = require('path');
-const webpack = require('webpack');
-const HtmlWebpackPlugin = require('html-webpack-plugin');
-const ExtractTextPlugin = require('extract-text-webpack-plugin');
-const ManifestPlugin = require('webpack-manifest-plugin');
-const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
-const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
-const eslintFormatter = require('react-dev-utils/eslintFormatter');
-const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
-const paths = require('./paths');
-const getClientEnvironment = require('./env');
-
-// Webpack uses `publicPath` to determine where the app is being served from.
-// It requires a trailing slash, or the file assets will get an incorrect path.
-const publicPath = paths.servedPath;
-// Some apps do not use client-side routing with pushState.
-// For these, "homepage" can be set to "." to enable relative asset paths.
-const shouldUseRelativeAssetPaths = publicPath === './';
-// Source maps are resource heavy and can cause out of memory issue for large source files.
-const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
-// `publicUrl` is just like `publicPath`, but we will provide it to our app
-// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
-// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
-const publicUrl = publicPath.slice(0, -1);
-// Get environment variables to inject into our app.
-const env = getClientEnvironment(publicUrl);
-
-// Assert this just to be safe.
-// 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';
-
-// ExtractTextPlugin expects the build output to be flat.
-// (See https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27)
-// However, our output is structured with css, js and media folders.
-// To have this structure working with relative paths, we have to use custom options.
-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.
-
-// console.log('publicPath ', publicPath)
-module.exports = {
- // Don't attempt to continue if there are any errors.
- bail: true,
- // We generate sourcemaps in production. This is slow but gives good results.
- // You can exclude the *.map files from the build during deployment.
- // devtool: shouldUseSourceMap ? 'nosources-source-map' : false, //正式版
- devtool: shouldUseSourceMap ? 'source-map' : false,//测试版
- // In production, we only want to load the polyfills and the app code.
- entry: [require.resolve('./polyfills'), paths.appIndexJs],
- output: {
- // The build folder.
- path: paths.appBuild,
- // Generated JS file names (with nested folders).
- // There will be one main bundle, and one file per asynchronous chunk.
- // We don't currently advertise code splitting but Webpack supports it.
- filename: './static/js/[name].[chunkhash:8].js',
- chunkFilename: './static/js/[name].[chunkhash:8].chunk.js',
- // We inferred the "public path" (such as / or /my-project) from homepage.
- // cdn
- // 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
- publicPath: '/react/build/', //publicPath, https://cdn.educoder.net
-
- // Point sourcemap entries to original disk location (format as URL on Windows)
- devtoolModuleFilenameTemplate: info =>
- path
- .relative(paths.appSrc, info.absoluteResourcePath)
- .replace(/\\/g, '/'),
- },
- resolve: {
- // 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"
- // if there are any conflicts. This matches Node resolution mechanism.
- // https://github.com/facebookincubator/create-react-app/issues/253
- modules: ['node_modules', paths.appNodeModules].concat(
- // It is guaranteed to exist because we tweak it in `env.js`
- process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
- ),
- // These are the reasonable defaults supported by the Node ecosystem.
- // We also include JSX as a common component filename extension to support
- // some tools, although we do not recommend using it, see:
- // https://github.com/facebookincubator/create-react-app/issues/290
- // `web` extension prefixes have been added for better support
- // for React Native Web.
- extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
- alias: {
- "educoder": __dirname + "/../src/common/educoder.js",
- // Support React Native Web
- // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
- 'react-native': 'react-native-web',
- },
- plugins: [
- // 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.
- // To fix this, we prevent you from importing files out of src/ -- if you'd like to,
- // 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: [
- // 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.
- // { parser: { requireEnsure: false } },
-
- // First, run the linter.
- // It's important to do this before Babel processes the JS.
- // {
- // test: /\.(js|jsx|mjs)$/,
- // enforce: 'pre',
- // use: [
- // {
- // options: {
- // formatter: eslintFormatter,
- // eslintPath: require.resolve('eslint'),
- //
- // },
- // loader: require.resolve('eslint-loader'),
- // },
- // ],
- // 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: [
- // "url" loader works just like "file" loader but it also embeds
- // assets smaller than specified size as data URLs to avoid requests.
- {
- test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
- loader: require.resolve('url-loader'),
- options: {
- limit: 10000,
- name: 'static/media/[name].[hash:8].[ext]',
- },
- },
- // Process JS with Babel.
- {
- test: /\.(js|jsx|mjs)$/,
- include: paths.appSrc,
- loader: require.resolve('babel-loader'),
- 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.
- // "style" loader normally turns CSS into JS modules injecting
+
+
+ );
+ }
+}
+
+export default Loading;
diff --git a/public/react/src/common/Const.js b/public/react/src/common/Const.js
index ed90302e3..f680cb78d 100644
--- a/public/react/src/common/Const.js
+++ b/public/react/src/common/Const.js
@@ -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_SHIXUN_MANAGER = 2 // 实训管理员
-export const EDU_SHIXUN_MEMBER = 3 // 实训成员
-export const EDU_CERTIFICATION_TEACHER = 4 // 平台认证的老师
-export const EDU_GAME_MANAGER = 5 // TPI的创建者
-export const EDU_TEACHER = 6 // 平台老师,但是未认证
-export const EDU_NORMAL = 7 // 普通用户
\ No newline at end of file
+export const EDU_BUSINESS = 2 // # 运营人员
+export const EDU_SHIXUN_MANAGER = 3 // 实训管理员
+export const EDU_SHIXUN_MEMBER = 4 // 实训成员
+export const EDU_CERTIFICATION_TEACHER = 5 // 平台认证的老师
+export const EDU_GAME_MANAGER = 6 // TPI的创建者
+export const EDU_TEACHER = 7 // 平台老师,但是未认证
+export const EDU_NORMAL = 8 // 普通用户
\ No newline at end of file
diff --git a/public/react/src/common/SnackbarHOC.js b/public/react/src/common/SnackbarHOC.js
index 1fd1407dd..d66b697ca 100644
--- a/public/react/src/common/SnackbarHOC.js
+++ b/public/react/src/common/SnackbarHOC.js
@@ -1,63 +1,81 @@
-import React, { Component } from 'react';
-import Snackbar from 'material-ui/Snackbar';
-import Fade from 'material-ui/transitions/Fade';
-
-export function SnackbarHOC(options = {}) {
- return function wrap(WrappedComponent) {
- return class Wrapper extends Component {
- constructor(props) {
- super(props);
- this.showSnackbar = this.showSnackbar.bind(this)
- this.state = {
- snackbarText: '',
- snackbarOpen: false,
- }
- }
-
- handleSnackbarClose() {
- this.setState({
- snackbarOpen: false,
- snackbarVertical: '',
- snackbarHorizontal: '',
- })
- }
-
- // 全局的snackbar this.props.showSnackbar调用即可
- showSnackbar(text, vertical, horizontal) {
- this.setState({
- snackbarOpen: true,
- snackbarText: text,
- snackbarVertical: vertical,
- snackbarHorizontal: horizontal,
- })
- }
- render() {
- const { snackbarOpen, snackbarText, snackbarHorizontal, snackbarVertical } = this.state;
-
-
- return (
-
- this.handleSnackbarClose()}
- transition={Fade}
- SnackbarContentProps={{
- 'aria-describedby': 'message-id',
- }}
- resumeHideDuration={2000}
- message={{this.state.snackbarText}}
- />
-
-
-
-
- )
- }
- }
- }
+import React, { Component } from 'react';
+import Snackbar from 'material-ui/Snackbar';
+import Fade from 'material-ui/transitions/Fade';
+import { notification } from 'antd'
+export function SnackbarHOC(options = {}) {
+ return function wrap(WrappedComponent) {
+ return class Wrapper extends Component {
+ constructor(props) {
+ super(props);
+ this.showSnackbar = this.showSnackbar.bind(this)
+ this.state = {
+ snackbarText: '',
+ snackbarOpen: false,
+ }
+ }
+
+ handleSnackbarClose() {
+ this.setState({
+ snackbarOpen: false,
+ snackbarVertical: '',
+ snackbarHorizontal: '',
+ })
+ }
+
+ // 全局的snackbar this.props.showSnackbar调用即可
+ // showSnackbar(description, message = "提示",icon) {
+ // // this.setState({
+ // // snackbarOpen: true,
+ // // snackbarText: text,
+ // // snackbarVertical: vertical,
+ // // snackbarHorizontal: horizontal,
+ // // })
+ // const data = {
+ // message,
+ // description
+ // }
+ // if (icon) {
+ // data.icon = icon;
+ // }
+ // notification.open(data);
+ // }
+
+ showSnackbar(text, vertical, horizontal) {
+ this.setState({
+ snackbarOpen: true,
+ snackbarText: text,
+ snackbarVertical: vertical,
+ snackbarHorizontal: horizontal,
+ })
+ }
+
+ render() {
+ const { snackbarOpen, snackbarText, snackbarHorizontal, snackbarVertical } = this.state;
+
+
+ return (
+
+ this.handleSnackbarClose()}
+ transition={Fade}
+ SnackbarContentProps={{
+ 'aria-describedby': 'message-id',
+ }}
+ resumeHideDuration={2000}
+ message={{this.state.snackbarText}}
+ />
+
+
+
+
+ )
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/public/react/src/common/TextUtil.js b/public/react/src/common/TextUtil.js
index 4a3145c19..c711143d2 100644
--- a/public/react/src/common/TextUtil.js
+++ b/public/react/src/common/TextUtil.js
@@ -1,5 +1,9 @@
import { bytesToSize } from 'educoder';
-export function markdownToHTML(oldContent) {
+export function isImageExtension(fileName) {
+ return fileName ? !!(fileName.match(/.(jpg|jpeg|png|gif)$/i)) : false
+}
+
+export function markdownToHTML(oldContent, selector) {
window.$('#md_div').html('')
// markdown to html
var markdwonParser = window.editormd.markdownToHTML("md_div", {
@@ -12,6 +16,9 @@ export function markdownToHTML(oldContent) {
sequenceDiagram: true // 默认不解析
});
const content = window.$('#md_div').html()
+ if (selector) {
+ window.$(selector).html(content)
+ }
return content
}
diff --git a/public/react/src/common/UnitUtil.js b/public/react/src/common/UnitUtil.js
index bb3267aa4..8b115dffb 100644
--- a/public/react/src/common/UnitUtil.js
+++ b/public/react/src/common/UnitUtil.js
@@ -1,6 +1,6 @@
-export function bytesToSize(bytes) {
- var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
- if (bytes == 0) return '0 Byte';
- var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
- return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i];
+export function bytesToSize(bytes) {
+ var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+ if (bytes == 0) return '0 Byte';
+ var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
+ return parseFloat(bytes / Math.pow(1024, i), 2).toFixed(1) + ' ' + sizes[i];
}
\ No newline at end of file
diff --git a/public/react/src/common/UrlTool.js b/public/react/src/common/UrlTool.js
index 2c307d13e..3953f1f34 100644
--- a/public/react/src/common/UrlTool.js
+++ b/public/react/src/common/UrlTool.js
@@ -1,40 +1,62 @@
-const isDev = window.location.port == 3007;
-export function getImageUrl(path) {
- // https://www.educoder.net
- // https://testbdweb.trustie.net
- // const local = 'http://localhost:3000'
- const local = 'https://testeduplus2.educoder.net'
- if (isDev) {
- return `${local}/${path}`
- }
- return `/${path}`;
-}
-
-export function getUrl(path, goTest) {
- // https://www.educoder.net
- // https://testbdweb.trustie.net
-
- // 如果想所有url定位到测试版,可以反注释掉下面这行
- //goTest = true
- // testbdweb.educoder.net testbdweb.trustie.net
- // const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000'
- const local = 'https://testeduplus2.educoder.net'
- if (isDev) {
- return `${local}${path?path:''}`
- }
- return `${path ? path: ''}`;
-}
-export function getUploadActionUrl(path, goTest) {
- return `${getUrl()}/api/attachments.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
-}
-
-export function test(path) {
- return `${path}`;
-}
-
-export function toPath(path) {
- window.open(path, '_blank');
-}
-
-
+const isDev = window.location.port == 3007;
+export function getImageUrl(path) {
+ // https://www.educoder.net
+ // https://testbdweb.trustie.net
+ // const local = 'http://localhost:3000'
+ const local = 'http://47.96.87.25:48080'
+ if (isDev) {
+ return `${local}/${path}`
+ }
+ return `/${path}`;
+}
+
+export function setImagesUrl(path){
+ const local = 'http://47.96.87.25:48080'
+ let firstStr=path.substr(0,1);
+ console.log(firstStr);
+ if(firstStr=="/"){
+ return isDev?`${local}${path}`:`${path}`;
+ }else{
+ return isDev?`${local}/${path}`:`/${path}`;
+ }
+}
+
+export function getUrl(path, goTest) {
+ // https://www.educoder.net
+ // https://testbdweb.trustie.net
+
+ // 如果想所有url定位到测试版,可以反注释掉下面这行
+ //goTest = true
+ // testbdweb.educoder.net testbdweb.trustie.net
+ // const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000'
+ // const local = 'https://testeduplus2.educoder.net'
+ const local = 'http://47.96.87.25:48080'
+ if (isDev) {
+ return `${local}${path?path:''}`
+ }
+ return `${path ? path: ''}`;
+}
+export function getUrl2(path, goTest) {
+ const local = 'http://localhost:3000'
+ if (isDev) {
+ return `${local}${path?path:''}`
+ }
+ return `${path ? path: ''}`;
+}
+export function getUploadActionUrl(path, goTest) {
+ return `${getUrl()}/api/attachments.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
+}
+export function getUploadActionUrlOfAuth(id) {
+ return `${getUrl()}/api/users/accounts/${id}/auth_attachments.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
+}
+
+export function test(path) {
+ return `${path}`;
+}
+
+export function toPath(path) {
+ window.open(path, '_blank');
+}
+
+
// export default queryString
\ No newline at end of file
diff --git a/public/react/src/common/components/Cropper.js b/public/react/src/common/components/Cropper.js
new file mode 100644
index 000000000..632950434
--- /dev/null
+++ b/public/react/src/common/components/Cropper.js
@@ -0,0 +1,288 @@
+import React, { Component } from 'react';
+
+import { getUrl2, isDev } from 'educoder'
+const $ = window.$
+
+let _url_origin = getUrl2()
+// let _url_origin = `http://47.96.87.25:48080`;
+
+
+
+function save_avatar(){
+
+ // if($(img_lg).html().trim() == ""){
+ // $("#avatar-name").html("请先选择图片上传").css("color", 'red');
+ // } else {
+ // $("#avatar-name").html("").css("color", '#333');
+ const previewId = this.props.previewId
+ var img_lg = document.getElementById(previewId || 'img-preview');
+ // 截图小的显示框内的内容
+ window.html2canvas(img_lg).then(function(canvas) {
+ // for test
+ // document.getElementById('canvasWrap').appendChild(canvas);
+
+ var dataUrl = canvas.toDataURL("image/jpeg");
+ console.log(dataUrl)
+ // TODO upload base64 image data to server
+ });
+ return
+
+ // 老版接口:
+ // html2canvas(img_lg, {
+ // allowTaint: true,
+ // taintTest: false,
+ // onrendered: function(canvas) {
+ // canvas.id = "mycanvas";
+ // //生成base64图片数据
+ // var dataUrl = canvas.toDataURL("image/jpeg");
+ // console.log(dataUrl)
+
+ // var newImg = document.getElementById("showImg");
+ // newImg.src = dataUrl;
+ // return;
+
+ // imagesAjax(dataUrl);
+ // $(".avatar-save").attr("disabled","true");
+ // }
+ // });
+ // }
+}
+/**
+ props 说明:
+ imageId 源图片标签的id
+ previewId crop后预览dom的id
+ imageSrc 源图片src
+ width 数字格式
+ height 数字格式
+*/
+class Cropper extends Component {
+ state = {
+ };
+
+ handleChange = (info) => {
+ }
+
+ componentDidMount() {
+ this.options = {
+ aspectRatio: 1,
+ crop(event) {
+ // console.log(event.detail.x);
+ // console.log(event.detail.y);
+ // console.log(event.detail.width);
+ // console.log(event.detail.height);
+ // console.log(event.detail.rotate);
+ // console.log(event.detail.scaleX);
+ // console.log(event.detail.scaleY);
+ },
+ preview: this.props.previewId ? `#${this.props.previewId}` : '.img-preview',
+ }
+
+ if (!window.Cropper) {
+ $.ajaxSetup({
+ cache: true
+ });
+ const _isDev = isDev()
+ let _path = _isDev ? 'public' : 'build'
+
+ $('head').append($('')
+ .attr('href', `${_url_origin}/react/${_path}/js/cropper/cropper.min.css`));
+
+ $.getScript(
+ `${_url_origin}/react/${_path}/js/cropper/cropper.js`,
+ (data, textStatus, jqxhr) => {
+
+ });
+ $.getScript(
+ `${_url_origin}/react/${_path}/js/cropper/html2canvas.min.js`,
+ (data, textStatus, jqxhr) => {
+
+ });
+ }
+
+ setTimeout(() => {
+ const image = document.getElementById(this.props.imageId || '__image');
+ this.cropper = new window.Cropper(image, this.options);
+ }, 1000)
+ }
+
+ renew = (image) => {
+ this.cropper && this.cropper.destroy();
+ this.cropper = new window.Cropper(image, this.options);
+
+ }
+ render() {
+
+ const { width, height, previewId, imageSrc } = this.props;
+
+ return (
+
+ {/* This rule is very important, please do not ignore this! */}
+
+
+ {/* http://localhost:3007/images/footNavLogo.png 图片转了后不对
+ || "/images/testPicture.jpg"
+ || "/images/shixun0.jpg"
+ */}
+
+
+ {/* background: 'aquamarine',
+ 'border-radius': '128px'
+ */}
+ {!previewId &&
+
}
+
+ {/*
*/}
+
+ {/*
*/}
+ {/*
*/}
+
+ );
+ }
+}
+
+export default Cropper;
+
+
+// function aaa () {
+// function showedit_headphoto() {
+// var html = `
+//