@ -0,0 +1,153 @@
|
||||
# encoding=utf-8
|
||||
|
||||
module Mobile
|
||||
module Apis
|
||||
class ProjectPackages < Grape::API
|
||||
use Mobile::Middleware::NotFoundHandler
|
||||
|
||||
helpers do
|
||||
def current_package
|
||||
@_current_package ||= ProjectPackage.find(params[:id])
|
||||
end
|
||||
|
||||
def symbolize_params
|
||||
params.to_hash.symbolize_keys
|
||||
end
|
||||
end
|
||||
|
||||
resources :project_packages do
|
||||
desc 'project packages index'
|
||||
params do
|
||||
optional :category, type: String, desc: '类型'
|
||||
optional :keyword, type: String, desc: '搜索关键字'
|
||||
optional :sort_by, type: String, desc: '排序'
|
||||
optional :sort_direction, type: String, desc: '排序方向'
|
||||
optional :page, type: Integer, desc: '页数'
|
||||
optional :per_page, type: Integer, desc: '分页大小'
|
||||
end
|
||||
get do
|
||||
packages = ProjectPackage.where(status: %w(published bidding_ended bidding_finished))
|
||||
|
||||
packages = packages.where(category: params[:category]) if params[:category].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, bidding_users: :user)
|
||||
|
||||
present :count, count
|
||||
present :project_packages, packages, with: Mobile::Entities::ProjectPackage, type: :index, user: current_user
|
||||
end
|
||||
|
||||
|
||||
desc 'project package show'
|
||||
params do
|
||||
requires :id, type: Integer, desc: 'ID'
|
||||
end
|
||||
route_param :id do
|
||||
get do
|
||||
authenticate!
|
||||
user = current_user
|
||||
|
||||
unless current_package.visitable? || user.id == current_package.creator_id || user.admin?
|
||||
error!('403 forbidden', 403)
|
||||
end
|
||||
|
||||
present current_package, with: Mobile::Entities::ProjectPackage, type: :show, user: user
|
||||
end
|
||||
|
||||
resource :bidding_users do
|
||||
desc 'user bidding'
|
||||
params {}
|
||||
post do
|
||||
authenticate!
|
||||
begin
|
||||
::ProjectPackages::BiddingService.new(current_package, current_user).call
|
||||
render_ok
|
||||
rescue ::ProjectPackages::BiddingService::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'select bidding user win'
|
||||
params do
|
||||
requires :user_ids, type: Array[Integer], desc: '中标者用户ID数组'
|
||||
end
|
||||
post 'win' do
|
||||
authenticate!
|
||||
begin
|
||||
package = current_user.project_packages.find(params[:id])
|
||||
ProjectPackages::WinBiddingService.new(package, symbolize_params).call
|
||||
rescue ProjectPackages::WinBiddingService::Error =>ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc 'create project packages'
|
||||
params do
|
||||
requires :category, type: String, desc: '类型'
|
||||
requires :title, type: String, desc: '标题'
|
||||
requires :content, type: String, desc: '描述'
|
||||
optional :attachment_ids, type: Array[Integer], desc: '附件ID数组'
|
||||
requires :deadline_at, type: DateTime, desc: '截止日期'
|
||||
requires :min_price, type: Float, desc: '最小费用'
|
||||
optional :max_price, type: Float, desc: '最大费用'
|
||||
requires :contact_name, type: String, desc: '联系人姓名'
|
||||
requires :contact_phone, type: String, desc: '联系人手机号'
|
||||
optional :code, type: String, desc: '验证码'
|
||||
optional :publish, type: Boolean, desc: '是否申请发布'
|
||||
end
|
||||
post do
|
||||
authenticate!
|
||||
begin
|
||||
package = current_user.project_packages.new
|
||||
::ProjectPackages::SaveService.new(package, symbolize_params).call
|
||||
package.increment_visit_count!
|
||||
render_ok(id: package.id)
|
||||
rescue ::ProjectPackages::SaveService::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc 'update project package'
|
||||
params do
|
||||
requires :id, type: Integer, desc: 'ID'
|
||||
requires :category, type: String, desc: '类型'
|
||||
requires :title, type: String, desc: '标题'
|
||||
requires :content, type: String, desc: '描述'
|
||||
optional :attachment_ids, type: Array[Integer], desc: '附件ID数组'
|
||||
requires :deadline_at, type: DateTime, desc: '截止日期'
|
||||
requires :min_price, type: Float, desc: '最小费用'
|
||||
optional :max_price, type: Float, desc: '最大费用'
|
||||
requires :contact_name, type: String, desc: '联系人姓名'
|
||||
requires :contact_phone, type: String, desc: '联系人手机号'
|
||||
optional :code, type: String, desc: '验证码'
|
||||
optional :publish, type: Boolean, desc: '是否申请发布'
|
||||
end
|
||||
put ':id' do
|
||||
authenticate!
|
||||
begin
|
||||
package = current_user.project_packages.find(params[:id])
|
||||
return render_error('该状态下不能编辑') unless package.editable?
|
||||
|
||||
::ProjectPackages::SaveService.new(package, symbolize_params).call
|
||||
package.increment_visit_count!
|
||||
render_ok(id: package.id)
|
||||
rescue ::ProjectPackages::SaveService::Error => ex
|
||||
render_error(ex.message)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,72 @@
|
||||
module Mobile
|
||||
module Entities
|
||||
class ProjectPackage < Grape::Entity
|
||||
include ApplicationHelper
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
expose :id
|
||||
expose :title
|
||||
expose :category
|
||||
expose :status
|
||||
expose :visit_count, if: { type: :index }
|
||||
expose :bidding_users_count, if: { type: :index }
|
||||
expose :min_price
|
||||
expose :max_price
|
||||
expose :contact_name, if: ->(package, opts){ opts[:user].id == package.creator_id || opts[:user].admin? || opts[:user].business? }
|
||||
expose :contact_phone, if: ->(package, opts){ opts[:user].id == package.creator_id || opts[:user].admin? || opts[:user].business? }
|
||||
|
||||
expose :deadline_at do |package, _|
|
||||
package.deadline_at.try(:utc).try(:iso8601)
|
||||
end
|
||||
expose :published_at do |package, _|
|
||||
package.published_at.try(:utc).try(:iso8601)
|
||||
end
|
||||
|
||||
expose :creator, if: { type: :show } do |package, _|
|
||||
{
|
||||
id: package.creator.id,
|
||||
login: package.creator.login,
|
||||
name: package.creator.show_name,
|
||||
avatar_url: "/images/#{url_to_avatar(package.creator)}"
|
||||
}
|
||||
end
|
||||
|
||||
expose :attachments, if: { type: :show } do |package, _|
|
||||
package.attachments.map do |attachment|
|
||||
{
|
||||
id: attachment.id,
|
||||
title: attachment.title,
|
||||
filesize: number_to_human_size(attachment.filesize),
|
||||
url: "attachments/download/#{attachment.id}",
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
expose :bidding_users, if: { type: :show } do |package, _|
|
||||
package.bidding_users.map do |bidding_user|
|
||||
user = bidding_user.user
|
||||
{
|
||||
id: user.id,
|
||||
login: user.login,
|
||||
name: user.show_name,
|
||||
avatar_url: "/images/#{url_to_avatar(user)}",
|
||||
status: bidding_user.status
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
expose :operation, if: { type: :show } do |package, opts|
|
||||
user = opts[:user]
|
||||
is_creator = user.id == package.creator_id
|
||||
is_admin = user.admin? || user.business?
|
||||
|
||||
{
|
||||
can_bidding: package.can_bidding?(user),
|
||||
can_select_bidding_user: package.bidding_end? && package.end_bidding? && (is_creator || is_admin),
|
||||
can_edit: package.editable? && (is_creator || is_admin),
|
||||
can_delete: package.deletable? && (is_creator || is_admin)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,20 @@
|
||||
#coding=utf-8
|
||||
|
||||
|
||||
module Mobile
|
||||
module Middleware
|
||||
class NotFoundHandler < Grape::Middleware::Base
|
||||
def call!(env)
|
||||
@env = env
|
||||
begin
|
||||
@app.call(@env)
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
message = {status: 404, message: e.message }.to_json
|
||||
status = 200
|
||||
headers = { 'Content-Type' => content_type }
|
||||
Rack::Response.new([message], status, headers).finish
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,46 @@
|
||||
class Managements::ProjectPackageAppliesController < Managements::BaseController
|
||||
before_filter :set_menu_type
|
||||
|
||||
def index
|
||||
applies = ProjectPackageApply.order('project_package_applies.updated_at desc')
|
||||
|
||||
search = params[:search].to_s.strip
|
||||
if search.present?
|
||||
applies = applies.joins(:project_package).where('project_packages.title like :search', search: "%#{search}%")
|
||||
end
|
||||
|
||||
applies = applies.where(status: params[:status].presence || :pending)
|
||||
|
||||
@applies = paginateHelper applies.includes(project_package: { creator: :user_extensions })
|
||||
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.html
|
||||
end
|
||||
end
|
||||
|
||||
def agree
|
||||
ProjectPackages::AgreeApplyService.new(current_apply).call
|
||||
render json: { status: 0 }
|
||||
rescue ProjectPackages::AgreeApplyService::Error => e
|
||||
render json: { status: -1, message: e.message }
|
||||
end
|
||||
|
||||
def refuse
|
||||
ProjectPackages::RefuseApplyService.new(current_apply, reason: params[:reason]).call
|
||||
render json: { status: 0 }
|
||||
rescue ProjectPackages::RefuseApplyService::Error => e
|
||||
render json: { status: -1, message: e.message }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_apply
|
||||
@_current_apply ||= ProjectPackageApply.find(params[:id])
|
||||
end
|
||||
|
||||
def set_menu_type
|
||||
@menu_type = 10
|
||||
@sub_type = 9
|
||||
end
|
||||
end
|
@ -0,0 +1,55 @@
|
||||
class Managements::ProjectPackagesController < Managements::BaseController
|
||||
before_filter :set_menu_type, only: [:index]
|
||||
|
||||
def index
|
||||
packages = ProjectPackage.where(nil)
|
||||
|
||||
# 任务标题
|
||||
keyword = params[:keyword].to_s.strip
|
||||
packages = packages.where('title LIKE ?', "%#{keyword}%") if keyword.present?
|
||||
|
||||
# 发布者姓名
|
||||
creator_name = params[:creator_name].to_s.strip
|
||||
if creator_name.present?
|
||||
sql = 'LOWER(concat(users.lastname, users.firstname)) LIKE ?'
|
||||
packages = packages.joins(:creator).where(sql, "%#{creator_name}%")
|
||||
end
|
||||
|
||||
# 状态
|
||||
status =
|
||||
case params[:status]
|
||||
when 'pending' then %w(pending refused)
|
||||
when 'applying' then %w(applying)
|
||||
when 'published' then %w(published bidding_end)
|
||||
when 'finished' then %w(bidding_finished)
|
||||
end
|
||||
packages = packages.where(status: status) if status.present?
|
||||
|
||||
# 发布时间
|
||||
begin_date = (params[:begin_date].to_time.beginning_of_day rescue nil)
|
||||
end_date = (params[:end_date].to_time.end_of_day rescue nil)
|
||||
packages = packages.where('published_at >= ?', begin_date) if begin_date.present?
|
||||
packages = packages.where('published_at <= ?', end_date) if end_date.present?
|
||||
|
||||
@count = packages.count
|
||||
|
||||
# 排序
|
||||
params[:sort_by] ||= 'created_at'
|
||||
params[:sort_direction] ||= 'desc'
|
||||
packages = packages.order("#{params[:sort_by]} #{params[:sort_direction]}")
|
||||
|
||||
@packages = paginateHelper packages.preload(:creator)
|
||||
end
|
||||
|
||||
def destroy
|
||||
ProjectPackage.find(params[:id]).destroy
|
||||
render json: { status: 0 }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_menu_type
|
||||
@menu_type = 14
|
||||
@sub_type = 1
|
||||
end
|
||||
end
|
@ -0,0 +1,45 @@
|
||||
# encoding=utf-8
|
||||
# For react
|
||||
class ProjectPackagesController < ApplicationController
|
||||
before_filter :require_login, :except => [:index]
|
||||
|
||||
include ApplicationHelper
|
||||
|
||||
def show
|
||||
render_react
|
||||
end
|
||||
|
||||
def new
|
||||
render_react
|
||||
end
|
||||
|
||||
def index
|
||||
render_react
|
||||
end
|
||||
|
||||
def edit
|
||||
render_react
|
||||
end
|
||||
|
||||
def apply_success
|
||||
render_react
|
||||
end
|
||||
|
||||
def destroy
|
||||
package = ProjectPackage.find(params[:id])
|
||||
return render_403 unless package.deletable? && (admin_or_business? || package.creator_id == current_user.id)
|
||||
|
||||
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 json: { status: 0, message: 'success' }
|
||||
end
|
||||
|
||||
private
|
||||
def render_react
|
||||
render file: 'public/react/build/index.html', :layout => false
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,24 @@
|
||||
class BiddingUser < ActiveRecord::Base
|
||||
include AASM
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :project_package, counter_cache: true
|
||||
|
||||
aasm(:status) do
|
||||
state :pending, initiali: true
|
||||
state :bidding_won
|
||||
state :bidding_lost
|
||||
|
||||
event :win do
|
||||
transitions from: [:pending], to: :bid_won
|
||||
end
|
||||
|
||||
event :lose do
|
||||
transitions from: [:pending], to: :bid_lost
|
||||
end
|
||||
end
|
||||
|
||||
def status_text
|
||||
I18n.t("bidding_user.status.#{status}")
|
||||
end
|
||||
end
|
@ -0,0 +1,96 @@
|
||||
class ProjectPackage < ActiveRecord::Base
|
||||
include AASM
|
||||
|
||||
acts_as_attachable
|
||||
|
||||
CATEGORY_VALUES = %w(front backend mobile database cloud_compute_and_big_data devops_and_test ai other)
|
||||
|
||||
attr_accessible :title, :content, :category, :deadline_at, :contact_name, :contact_phone, :min_price, :max_price
|
||||
|
||||
belongs_to :creator, class_name: 'User'
|
||||
|
||||
has_many :project_package_applies, dependent: :destroy
|
||||
has_one :process_project_package_apply, conditions: { status: :pending }, class_name: 'ProjectPackageApply'
|
||||
|
||||
has_many :bidding_users, dependent: :delete_all
|
||||
has_many :win_bidding_users, conditions: { status: :bidding_won }, class_name: 'BiddingUser'
|
||||
has_many :lose_bidding_users, conditions: { status: :bidding_lost }, class_name: 'BiddingUser'
|
||||
|
||||
has_many :attachments, as: :container, dependent: :destroy
|
||||
|
||||
validates :category, presence: true, inclusion: { in: CATEGORY_VALUES }
|
||||
validates :title, presence: true, length: { maximum: 60 }
|
||||
validates :content, presence: true
|
||||
validates :deadline_at, presence: true
|
||||
validates :contact_name, presence: true, length: { maximum: 20 }
|
||||
validates :contact_phone, presence: true, format: { with: /1\d{10}/ }
|
||||
validates :min_price, numericality: { greater_than: 0 }
|
||||
validates :max_price, numericality: { greater_than: ->(obj){ obj.min_price } }, allow_blank: true
|
||||
|
||||
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 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?
|
||||
end_bidding! if flag && may_end_bidding?
|
||||
flag
|
||||
end
|
||||
|
||||
def can_bidding?(user)
|
||||
published? && !bidding_end? && !bidding_users.exists?(user_id: user.id)
|
||||
end
|
||||
|
||||
def increment_visit_count!
|
||||
ProjectPackage.connection.execute("update project_packages set visit_count = COALESCE(visit_count, 0) + 1 where id = #{id}")
|
||||
end
|
||||
|
||||
def category_text
|
||||
I18n.t("project_package.category.#{category}")
|
||||
end
|
||||
|
||||
def status_text
|
||||
I18n.t("project_package.status.#{status}")
|
||||
end
|
||||
end
|
@ -0,0 +1,21 @@
|
||||
class ProjectPackageApply < ActiveRecord::Base
|
||||
include AASM
|
||||
|
||||
attr_accessible :project_package_id, :reason
|
||||
|
||||
belongs_to :project_package
|
||||
|
||||
aasm(:status) do
|
||||
state :pending, initiali: true
|
||||
state :refused
|
||||
state :agreed
|
||||
|
||||
event :refuse do
|
||||
transitions from: :pending, to: :refused
|
||||
end
|
||||
|
||||
event :agree do
|
||||
transitions from: :pending, to: :agreed
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,36 @@
|
||||
class ProjectPackages::AgreeApplyService
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
attr_reader :apply, :package
|
||||
|
||||
def initialize(apply)
|
||||
@apply = apply
|
||||
@package = apply.project_package
|
||||
end
|
||||
|
||||
def call
|
||||
raise Error, '该状态下不能进行此操作' unless apply.may_agree? && package.may_publish?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
apply.agree!
|
||||
|
||||
# 发布
|
||||
package.publish
|
||||
package.published_at = Time.now
|
||||
package.save!
|
||||
|
||||
# 消息
|
||||
send_agree_notify!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def send_agree_notify!
|
||||
Tiding.where(container_id: package.id, container_type: 'ProjectPackage',
|
||||
tiding_type: 'Apply', status: 0).update_all(status: 1)
|
||||
|
||||
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
|
||||
container_id: package.id, container_type: 'ProjectPackage',
|
||||
tiding_type: 'System', status: 1)
|
||||
end
|
||||
end
|
@ -0,0 +1,31 @@
|
||||
class ProjectPackages::ApplyPublishService
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
attr_reader :package
|
||||
|
||||
def initialize(package)
|
||||
@package = package
|
||||
end
|
||||
|
||||
def call
|
||||
return if package.applying?
|
||||
|
||||
raise Error, '该状态下不能申请发布' unless package.may_apply?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
package.apply!
|
||||
|
||||
package.project_package_applies.create!
|
||||
|
||||
send_project_package_apply_notify!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_project_package_apply_notify!
|
||||
Tiding.create!(user_id: 1, trigger_user_id: package.creator_id,
|
||||
container_id: package.id, container_type: 'ProjectPackage',
|
||||
tiding_type: 'Apply', status: 0)
|
||||
end
|
||||
end
|
@ -0,0 +1,29 @@
|
||||
class ProjectPackages::BiddingService
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
attr_reader :package, :user
|
||||
|
||||
def initialize(package, user)
|
||||
@package = package
|
||||
@user = user
|
||||
end
|
||||
|
||||
def call
|
||||
raise Error, '竞标已截止' if package.bidding_end?
|
||||
raise Error, '不能参与自己发布的竞标' if package.creator_id == user.id
|
||||
raise Error, '您已参与竞标' if package.bidding_users.exists?(user_id: user.id)
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
package.bidding_users.create!(user_id: user.id)
|
||||
|
||||
send_bidding_notify!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_bidding_notify!
|
||||
Tiding.create!(user_id: package.creator_id, trigger_user_id: user.id,
|
||||
container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'Bidding')
|
||||
end
|
||||
end
|
@ -0,0 +1,26 @@
|
||||
class ProjectPackages::EndBiddingService
|
||||
attr_reader :package
|
||||
|
||||
def initialize(package)
|
||||
@package = package
|
||||
end
|
||||
|
||||
def call
|
||||
return unless package_deadline?
|
||||
|
||||
package.end_bidding!
|
||||
|
||||
send_bidding_end_notify!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_bidding_end_notify!
|
||||
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
|
||||
container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'BiddingEnd')
|
||||
end
|
||||
|
||||
def package_deadline?
|
||||
package.may_end_bidding? && package.deadline_at < Time.now
|
||||
end
|
||||
end
|
@ -0,0 +1,38 @@
|
||||
class ProjectPackages::RefuseApplyService
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
attr_reader :apply, :package, :params
|
||||
|
||||
def initialize(apply, params)
|
||||
@apply = apply
|
||||
@package = apply.project_package
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
raise Error, '该状态下不能进行此操作' unless apply.may_refuse? && package.may_refuse?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
apply.refuse
|
||||
apply.reason = params[:reason].to_s.strip
|
||||
apply.save!
|
||||
|
||||
# 发布
|
||||
package.refuse!
|
||||
|
||||
# 消息
|
||||
send_refuse_notify!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_refuse_notify!
|
||||
Tiding.where(container_id: package.id, container_type: 'ProjectPackage',
|
||||
tiding_type: 'Apply', status: 0).update_all(status: 1)
|
||||
|
||||
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
|
||||
container_id: package.id, container_type: 'ProjectPackage',
|
||||
tiding_type: 'System', status: 2, extra: apply.reason)
|
||||
end
|
||||
end
|
@ -0,0 +1,67 @@
|
||||
class ProjectPackages::SaveService
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
attr_reader :package, :params
|
||||
|
||||
def initialize(package, params)
|
||||
@package = package
|
||||
@params = params
|
||||
end
|
||||
|
||||
def call
|
||||
check_code_valid! if need_check_code?
|
||||
|
||||
is_create = package.new_record?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
package.assign_attributes(params)
|
||||
package.save!
|
||||
|
||||
# 处理附件
|
||||
deal_attachments
|
||||
|
||||
send_create_notify! if is_create
|
||||
|
||||
ProjectPackages::ApplyPublishService.new(package).call 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.contact_phone != params[:contact_phone]
|
||||
end
|
||||
|
||||
def check_code_valid!
|
||||
raise Error, '验证码不能为空' if params[:code].blank?
|
||||
|
||||
code = VerificationCode.where(phone: params[:contact_phone], code_type: 9, code: params[:code]).last
|
||||
raise Error, '无效的验证码' if code.blank? || !code.valid_code?
|
||||
end
|
||||
|
||||
def deal_attachments
|
||||
attachment_ids = Array.wrap(params[:attachment_ids]).compact.map(&:to_i) || []
|
||||
old_attachment_ids = package.attachments.pluck(:id)
|
||||
|
||||
destroy_ids = old_attachment_ids - attachment_ids
|
||||
package.attachments.where(id: destroy_ids).delete_all
|
||||
|
||||
new_ids = attachment_ids - old_attachment_ids
|
||||
if new_ids.present?
|
||||
Attachment.where(id: new_ids, container_id: nil).update_all(container_id: package.id, container_type: 'ProjectPackage')
|
||||
end
|
||||
end
|
||||
|
||||
def send_create_notify!
|
||||
Tiding.create!(user_id: package.creator_id, trigger_user_id: 1,
|
||||
container_id: package.id, container_type: 'ProjectPackage', tiding_type: 'Created')
|
||||
end
|
||||
|
||||
def with_publish?
|
||||
params[:publish].to_s == 'true'
|
||||
end
|
||||
end
|
@ -0,0 +1,47 @@
|
||||
class ProjectPackages::WinBiddingService
|
||||
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.can_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)
|
||||
sql = 'insert into tidings(user_id, trigger_user_id, container_id, container_type, tiding_type) values'
|
||||
base_opts = [package.creator_id, package.id, 'ProjectPackage', type]
|
||||
|
||||
values = '(' + user_ids.map { |id| base_opts.clone.unshift(id).join(',') }.join('),(') + ')'
|
||||
|
||||
sql + values
|
||||
end
|
||||
end
|
@ -0,0 +1,12 @@
|
||||
class CheckProjectPackageDeadlineTask
|
||||
def call
|
||||
ProjectPackage.where(status: :published).where('deadline_at < ?', Time.now).find_each do |package|
|
||||
begin
|
||||
ProjectPackages::EndBiddingService.new(package).call
|
||||
rescue => ex
|
||||
Rails.logger.error ex.message
|
||||
Rails.logger.error ex.backtrace.join('\n')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,120 @@
|
||||
<div class="edu-class-container mb15">
|
||||
<div class="edu-con-top clearfix">
|
||||
<p class="ml15 fl color-grey">众包需求发布</p>
|
||||
</div>
|
||||
<div class="edu-con-bg01 mt15">
|
||||
<div class="edu-tab clearfix mb20">
|
||||
<ul id="edu-tab-nav" class="border-bottom-orange">
|
||||
<li id="edu-tab-nav-1" class="new-tab-nav background-orange" onclick="HoverLi(1);">
|
||||
<%= link_to "待审批", project_package_applies_path(status: :pending), class: 'tab_type', remote: true %>
|
||||
</li>
|
||||
<li id="edu-tab-nav-2" class="new-tab-nav" onclick="HoverLi(2);">
|
||||
<%= link_to "已审批", project_package_applies_path(status: [:refused, :agreed]), class: 'tab_type', remote: true %>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="cl"></div>
|
||||
<div id="edu-tab-con-1">
|
||||
<div class="mt10">
|
||||
<div class="edu-position fr task-form-30 mb10 mr15">
|
||||
<input class="task-form-100 panel-box-sizing" placeholder="输入需求标题进行检索" type="text" id="search_name">
|
||||
<a href="javascript:void(0);" class="edu-btn-search font-16 color-grey mt10" id="search"><i class="fa fa-search"></i></a>
|
||||
</div>
|
||||
<div class="cl"></div>
|
||||
<div id="authentication_list" class="auth_table">
|
||||
<%= render :partial => "managements/project_package_applies/project_package_apply_list"%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="edu-tab-con-2" class="undis">
|
||||
<div class="mt10">
|
||||
<p class="fl task-form-60 mt8 ml15 clearfix">
|
||||
<%= link_to "全部", project_package_applies_path(status: [:refused, :agreed]), :class => "edu-filter-cir-grey mr5 fl font-12 active", :id => "project_package_all_authentication", :remote => true %>
|
||||
<%= link_to "同意", project_package_applies_path(status: :agreed), :class => "edu-filter-cir-grey mr5 fl font-12", :id => "project_package_agree_authentication", :remote => true %>
|
||||
<%= link_to "拒绝", project_package_applies_path(status: :refused), :class => "edu-filter-cir-grey mr5 fl font-12", :id => "project_package_reject_authentication", :remote => true %>
|
||||
</p>
|
||||
<div class="edu-position fr task-form-30 mb10 fr mr15">
|
||||
<input class="task-form-100 panel-box-sizing" placeholder="输入需求标题进行检索" type="text" id="project_package_search_name">
|
||||
<a href="javascript:void(0);" class="edu-btn-search font-16 color-grey mt10" id="project_package_search"><i class="fa fa-search"></i></a>
|
||||
</div>
|
||||
<div class="cl"></div>
|
||||
<div id="project_package_authentication_list" class="auth_table">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
/* -------------------------- 拒绝 ------------------------------------ */
|
||||
function reject_project_package_authentication_reason(nThis){
|
||||
var reason = $(nThis).parent().parent().find('div');
|
||||
reason.find("input").val("");
|
||||
reason.toggle();
|
||||
}
|
||||
|
||||
/* -------------------------- 取消 ------------------------------------ */
|
||||
function project_package_hide_reject_reason(nThis){
|
||||
var reason = $(nThis).parent().parent();
|
||||
reason.find("input").val("");
|
||||
reason.hide();
|
||||
}
|
||||
|
||||
/* ------------------------- 提交拒绝原因 --------------------------------- */
|
||||
function project_package_submit_reject_reason(id, nThis){
|
||||
var nReason = $(nThis).parent().parent();
|
||||
var reason = nReason.find("input").val();
|
||||
if (reason == '') {
|
||||
alert('请输入原因');
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
url: '/managements/project_package_applies/' + id + '/refuse',
|
||||
type: 'post',
|
||||
data: {reason: reason},
|
||||
success: function(data){
|
||||
if (data && data.status != -1) {
|
||||
$('#authentication_list .admin-con-box.apply-' + id).remove();
|
||||
|
||||
if($('#authentication_list .admin-con-box').length == 0){
|
||||
location.reload();
|
||||
}
|
||||
} else {
|
||||
alert(data.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------- 按名字进行搜索(未审批) ----------------------------- */
|
||||
$("#search").live("click", function(){
|
||||
var iName = $("#search_name").val();
|
||||
$.ajax({
|
||||
url: "/managements/project_package_applies",
|
||||
dataType: 'script',
|
||||
data: { search: iName, status: 'pending' }
|
||||
});
|
||||
});
|
||||
|
||||
/* ------------------- 按名字进行搜索(已审批)-------------------- */
|
||||
$("#project_package_search").live("click", function(){
|
||||
var iName = $("#project_package_search_name").val();
|
||||
var id = $("#project_package_all_authentication").parent().find(".active").attr("id");
|
||||
var status = '';
|
||||
|
||||
if(id == "project_package_all_authentication"){
|
||||
status = ['refused', 'agreed'];
|
||||
}else if(id=="project_package_agree_authentication"){
|
||||
status = 'agreed';
|
||||
}else{
|
||||
status = 'refused';
|
||||
}
|
||||
$.ajax({
|
||||
url: "/managements/project_package_applies",
|
||||
dataType: 'script',
|
||||
data: { search: iName, status: status}
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
@ -0,0 +1,30 @@
|
||||
var nTabIcon_1 = $("#edu-tab-con-1");
|
||||
var nTabIcon_2 = $("#edu-tab-con-2");
|
||||
var nTabNav_1 = $("#edu-tab-nav-1");
|
||||
var nTabNav_2 = $("#edu-tab-nav-2");
|
||||
var nAudit = $("#project_package_all_authentication").parent();
|
||||
|
||||
<% if params[:status].to_s == 'pending' %>
|
||||
$("#authentication_list").html("<%= j( render :partial => "managements/project_package_applies/project_package_apply_list" ) %>");
|
||||
nTabNav_1.addClass("background-orange");
|
||||
nTabNav_2.removeClass("background-orange");
|
||||
nTabIcon_1.show();
|
||||
nTabIcon_2.hide();
|
||||
<% else %>
|
||||
$("#project_package_authentication_list").html("<%= j( render :partial => "managements/project_package_applies/project_package_apply_list" ) %>");
|
||||
nTabNav_1.removeClass("background-orange");
|
||||
nTabNav_2.addClass("background-orange");
|
||||
nTabIcon_1.hide();
|
||||
nTabIcon_2.show();
|
||||
/* -------------------------- 未审批(全部、同意、拒绝点击时动态样式) ------------------------------ */
|
||||
if(<%= params[:status].to_s == 'agreed' %>){
|
||||
nAudit.find(".active").removeClass("active");
|
||||
$("#project_package_agree_authentication").addClass("active");
|
||||
}else if(<%= params[:status].to_s == 'refused' %>){
|
||||
nAudit.find(".active").removeClass("active");
|
||||
$("#project_package_reject_authentication").addClass("active");
|
||||
}else{
|
||||
nAudit.find(".active").removeClass("active");
|
||||
$("#project_package_all_authentication").addClass("active");
|
||||
}
|
||||
<% end %>
|
@ -0,0 +1,53 @@
|
||||
<div class="list-count">共<span><%= @obj_count %></span>条搜索结果</div>
|
||||
|
||||
<table class="edu-pop-table edu-txt-center" cellpadding="0" cellspacing="0" style="table-layout: fixed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="6%">序号</th>
|
||||
<th width="20%" class="edu-txt-left">任务标题</th>
|
||||
<th width="8%">状态</th>
|
||||
<th width="8%">竞标人数</th>
|
||||
<th width="8%">发布者</th>
|
||||
<th width="12%"><%= sort_tag('创建时间', name: 'created_at', path: managements_project_packages_path) %></th>
|
||||
<th width="12%"><%= sort_tag('发布时间', name: 'published_at', path: managements_project_packages_path) %></th>
|
||||
<th width="12%"><%= sort_tag('竞标截止时间', name: 'deadline_at', path: managements_project_packages_path) %></th>
|
||||
<th width="14%">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% if @packages.present? %>
|
||||
<% @packages.each_with_index do |package, index| %>
|
||||
<tr class="project-package-item project-package-<%= package.id %>">
|
||||
<td><%= (@obj_pages.page - 1) * @obj_pages.per_page + index + 1 %></td>
|
||||
<td class="edu-txt-left">
|
||||
<%= link_to package.title, project_package_path(package), target: '_blank',
|
||||
style: "display: block;max-width: 250px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;",
|
||||
data: { tip_down: package.title } %>
|
||||
</td>
|
||||
<td><%= package.status_text %></td>
|
||||
<td><%= package.bidding_users_count %></td>
|
||||
<td><%= package.creator.show_real_name %></td>
|
||||
<td><%= package.created_at.strftime('%Y-%m-%d %H:%M') %></td>
|
||||
<td><%= package.published_at.try(:strftime, '%Y-%m-%d %H:%M') || '--' %></td>
|
||||
<td><%= package.deadline_at.try(:strftime, '%Y-%m-%d %H:%M') || '--' %></td>
|
||||
<td> <%= link_to '删除', 'javascript:void(0)', class: 'delete-btn', data: { id: package.id } %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<tr>
|
||||
<td colspan="100">
|
||||
<%= render :partial => "welcome/no_data" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="text-align:center;" class="new_expand">
|
||||
<div class="pages_user_show" style="width:auto; display:inline-block;margin: 18px 0;">
|
||||
<ul id="school_report_ref_pages">
|
||||
<%= pagination_links_full @obj_pages, @obj_count, per_page_links: false, remote: true, flag: true, is_new: true, path: managements_project_packages_path(params.except(:page)) %>
|
||||
</ul>
|
||||
<div class="cl"></div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,115 @@
|
||||
<div class="edu-con-top clearfix xmt10 bor-grey-e mt10 padding10-20">
|
||||
<%= form_tag(managements_project_packages_path,method: :get, remote: true, id: '', class: 'management-project-packages-form' ) do %>
|
||||
<select name="status" class="fl winput-100-30">
|
||||
<option value="">全部状态</option>
|
||||
<option value="pending">草稿</option>
|
||||
<option value="applying">待审核</option>
|
||||
<option value="published">竞标中</option>
|
||||
<option value="finished">已完成</option>
|
||||
</select>
|
||||
|
||||
<%= text_field_tag :keyword, params[:keyword], placeholder: '请输入任务标题进行搜索', class: 'fl winput-200-30 ml10' %>
|
||||
|
||||
<%= text_field_tag :creator_name, params[:creator_name], placeholder: '请输入发布者进行搜索',
|
||||
class: 'fl winput-200-30 ml10' %>
|
||||
|
||||
<div class="fl ml20 font-14">
|
||||
<span>发布时间:</span>
|
||||
<%= text_field_tag :begin_date, params[:begin_date],
|
||||
class: 'winput-150-30', placeholder: '请选择开始时间'%>
|
||||
<%= text_field_tag :end_date, params[:end_date],
|
||||
class: 'winput-150-30', placeholder: '请选择结束时间'%>
|
||||
</div>
|
||||
|
||||
<%= link_to '清除', 'javascript:clearSearchCondition()', class: 'fr task-btn ml5 reset-btn' %>
|
||||
<%= link_to '搜索', 'javascript:void(0)', class: 'fr task-btn task-btn-orange ml5 search-btn' %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="edu-con-bg01 mt15" id="managements-project-packages-list" style="min-height: 400px;">
|
||||
<%= render partial: 'managements/project_packages/project_package_list' %>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
$('.search-btn').on('click', function() {
|
||||
$(".management-project-packages-form").submit();
|
||||
});
|
||||
|
||||
$('#managements-project-packages-list').on('click', '.delete-btn', function(){
|
||||
var id = $(this).data("id");
|
||||
op_confirm_tip_1("是否确认删除?", "deleteProjectPackage(" + id + ");")
|
||||
});
|
||||
|
||||
var locale = {
|
||||
clearText: '清除',
|
||||
clearStatus: '清除已选日期',
|
||||
closeText: '关闭',
|
||||
closeStatus: '不改变当前选择',
|
||||
prevText: '< 上月',
|
||||
prevStatus: '显示上月',
|
||||
prevBigText: '<<',
|
||||
prevBigStatus: '显示上一年',
|
||||
nextText: '下月>',
|
||||
nextStatus: '显示下月',
|
||||
nextBigText: '>>',
|
||||
nextBigStatus: '显示下一年',
|
||||
currentText: '今天',
|
||||
currentStatus: '显示本月',
|
||||
monthNames: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'],
|
||||
monthNamesShort: ['一月','二月','三月','四月','五月','六月', '七月','八月','九月','十月','十一月','十二月'],
|
||||
monthStatus: '选择月份',
|
||||
yearStatus: '选择年份',
|
||||
weekHeader: '周',
|
||||
weekStatus: '年内周次',
|
||||
dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
|
||||
dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
|
||||
dayNamesMin: ['日','一','二','三','四','五','六'],
|
||||
dayStatus: '设置 DD 为一周起始',
|
||||
dateStatus: '选择 m月 d日, DD',
|
||||
dateFormat: 'yy-mm-dd',
|
||||
firstDay: 1,
|
||||
initStatus: '请选择日期',
|
||||
isRTL: false
|
||||
};
|
||||
var baseOptions = {
|
||||
dateFormat: 'yy-mm-dd',
|
||||
maxDate: -1
|
||||
}
|
||||
var options = $.extend({}, locale, baseOptions)
|
||||
$('input[name="begin_date"]').datepicker(options);
|
||||
$('input[name="end_date"]').datepicker(options);
|
||||
});
|
||||
|
||||
function clearSearchCondition(){
|
||||
var form = $(".management-project-packages-form");
|
||||
form.find("select[name='status']").val("");
|
||||
form.find("input[name='keyword']").val("");
|
||||
form.find("input[name='creator_name']").val("");
|
||||
form.find("input[name='begin_date']").val("");
|
||||
form.find("input[name='end_date']").val("");
|
||||
|
||||
form.submit();
|
||||
}
|
||||
|
||||
function deleteProjectPackage(id){
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
url: "<%= managements_project_packages_path %>/" + id,
|
||||
success: function (data) {
|
||||
$('.popupAll').remove();
|
||||
|
||||
if(data && data.status == 0){
|
||||
$('#managements-project-packages-list .project-package-item.project-package-' + id).remove();
|
||||
|
||||
if($('#managements-project-packages-list .project-package-item').length == 0){
|
||||
$.ajax({ dataType: 'script', url: "<%= raw managements_project_packages_path(params.except(:controller, :action)) %>" });
|
||||
}
|
||||
} else {
|
||||
notice_box(data.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
</script>
|
@ -0,0 +1 @@
|
||||
$("#managements-project-packages-list").html("<%= j(render 'managements/project_packages/project_package_list') %>")
|
@ -0,0 +1,142 @@
|
||||
<% if @type == 'p_package' && User.current == @user %>
|
||||
<div class="edu-back-white padding20-30 clearfix secondNav educontent bor-top-greyE">
|
||||
<li class="<%= @p == 'a' ? 'active' : '' %>"><%= link_to "全部", user_path(@user, :type => @type), :remote => true %></li>
|
||||
<li class="<%= @p == '0' ? 'active' : '' %>"><%= link_to "未发布", user_path(@user, :type => @type, :p => "0"), :remote => true %></li>
|
||||
<li class="<%= @p == '1' ? 'active' : '' %>"><%= link_to "竞标中", user_path(@user, :type => @type, :p => "1"), :remote => true %></li>
|
||||
<li class="<%= @p == '2' ? 'active' : '' %>"><%= link_to "已完成", user_path(@user, :type => @type, :p => "2"), :remote => true %></li>
|
||||
</div>
|
||||
<% elsif @type == 'l_package' && User.current == @user %>
|
||||
<div class="edu-back-white padding20-30 clearfix secondNav educontent bor-top-greyE">
|
||||
<li class="<%= @p == 'a' ? 'active' : '' %>"><%= link_to "全部", user_path(@user, :type => @type, :p => "a"), :remote => true %></li>
|
||||
<li class="<%= @p == '0' ? 'active' : '' %>"><%= link_to "未中标", user_path(@user, :type => @type, :p => "0"), :remote => true %></li>
|
||||
<li class="<%= @p == '1' ? 'active' : '' %>"><%= link_to "已中标", user_path(@user, :type => @type, :p => "1"), :remote => true %></li>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="pl25 pr25 clearfix font-12 mt20 mb20 educontent">
|
||||
<span class="fl color-grey-9">共<%= @objects_count %>个</span>
|
||||
<div class="fr edu-menu-panel">
|
||||
<%= link_to '发布时间', user_path(@user, :order => @new_order, :sort => @sort, :type => @type), :class => "fl color-grey-9", :remote => true %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @objects_count > 0 %>
|
||||
<div class="educontent project-packages-list">
|
||||
<% @objects.each do |object| %>
|
||||
<% can_manage = @type == 'p_package' && (admin_or_business? || current_user.id == @user.id) %>
|
||||
<div class="project-package-item <%= can_manage ? 'with-operator' : '' %> project-package-<%= object.id %>">
|
||||
<div class="item-image">
|
||||
<%= image_tag("educoder/project_packages/#{object.category}.png") %>
|
||||
</div>
|
||||
<div class="item-body">
|
||||
<div class="item-head">
|
||||
<div class="item-head-title" data-tip-down="<%= object.title %>">
|
||||
<%= link_to object.title, project_package_path(object) %>
|
||||
</div>
|
||||
<div class="item-head-tags">
|
||||
<% if object.creator_id != @user.id %>
|
||||
<% bidding_user = object.bidding_users.find_by_user_id(@user.id) %>
|
||||
<%= raw content_tag(:span, bidding_user.status_text, class: bidding_user.status) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="item-head-blank"></div>
|
||||
<div class="item-head-price">
|
||||
<% if object.max_price && object.max_price != object.min_price %>
|
||||
<span>¥</span><%= object.min_price %>~<span>¥</span><%= object.max_price %>
|
||||
<% else %>
|
||||
<span>¥</span><%= object.min_price %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-category">
|
||||
<div class="item-category-item"><%= object.category_text %></div>
|
||||
</div>
|
||||
<div class="item-other">
|
||||
<div class="item-group item-other-visit">
|
||||
<span class="item-group-icon"><i class="fa fa-eye"></i></span>
|
||||
<span class="item-group-text"><%= object.visit_count %>人浏览</span>
|
||||
</div>
|
||||
<div class="item-group item-other-deadline">
|
||||
<% if object.published? %>
|
||||
<span class="item-group-icon"><i class="fa fa-clock-o"></i></span>
|
||||
<span class="item-group-text">
|
||||
<% if Time.now + 10.days > object.deadline_at %>
|
||||
<%= time_from_future(object.deadline_at) %>内竞标截止
|
||||
<% else %>
|
||||
<%= object.deadline_at.strftime('%Y-%m-%d') %> 竞标截止
|
||||
<% end %>
|
||||
</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="item-group item-other-bidding">
|
||||
<% if object.bidding_users_count > 0 %>
|
||||
<span class="item-group-icon"><i class="fa fa-user-o"></i></span>
|
||||
<span class="item-group-text"><%= object.bidding_users_count %>人竞标</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="item-other-blank"></div>
|
||||
<div class="item-group item-other-publish-at">
|
||||
<% if object.published_at.present? %>
|
||||
<span class="item-group-text">发布日期:<%= object.published_at.try(:strftime, '%Y-%m-%d') %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% if can_manage && (object.editable? || object.deletable?) %>
|
||||
<div class="item-operator">
|
||||
<% if object.editable? %>
|
||||
<a href="<%= edit_project_package_path(object) %>" title="编辑"><i class="fa fa-pencil"></i></a>
|
||||
<% end %>
|
||||
<% if object.deletable? %>
|
||||
<a href="javascript:void(0);"
|
||||
class="delete-project-package-btn"
|
||||
data-id="<%= object.id %>"
|
||||
title="删除"><i class="fa fa-trash-o"></i></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="educontent edu-txt-center mb80">
|
||||
<div class="inline pages_user_show">
|
||||
<ul>
|
||||
<%= pagination_links_full @obj_pages, @obj_count, :per_page_links => false, :remote => true, :flag => true, :is_new => true %>
|
||||
</ul>
|
||||
<div class="cl"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= render :partial => "welcome/no_data" %>
|
||||
<% end %>
|
||||
|
||||
<script>
|
||||
$(function(){
|
||||
$(".delete-project-package-btn").on("click", function(){
|
||||
var id = $(this).data("id")
|
||||
op_confirm_tip_1("是否确认删除?", "deleteProjectPackage(" + id + ");")
|
||||
});
|
||||
});
|
||||
|
||||
function deleteProjectPackage(id) {
|
||||
$.ajax({
|
||||
type: "DELETE",
|
||||
url: "<%= project_packages_path %>/" + id,
|
||||
success: function (data) {
|
||||
$('.popupAll').remove();
|
||||
|
||||
if(data && data.status == 0){
|
||||
$('.project-packages-list .project-package-item.project-package-' + id).remove();
|
||||
|
||||
if($('.project-packages-list .project-package-item').length == 0){
|
||||
$.ajax({ dataType: 'script', url: "<%= raw user_path(@user, params.except(:controller, :action)) %>" });
|
||||
}
|
||||
} else {
|
||||
notice_box(data.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
</script>
|
@ -0,0 +1,6 @@
|
||||
'zh':
|
||||
bidding_user:
|
||||
status:
|
||||
pending: 竞标中
|
||||
bidding_won: 已中标
|
||||
bidding_lost: 未中标
|
@ -0,0 +1,18 @@
|
||||
zh:
|
||||
project_package:
|
||||
category:
|
||||
front: 前端开发
|
||||
backend: 后端开发
|
||||
mobile: 移动开发
|
||||
database: 数据库
|
||||
cloud_compute_and_big_data: 云计算与大数据
|
||||
devops_and_test: 运维与测试
|
||||
ai: 人工智能
|
||||
other: 其它
|
||||
status:
|
||||
pending: 已创建
|
||||
applying: 审核中
|
||||
refused: 已拒绝
|
||||
published: 竞标中
|
||||
bidding_ended: 待选标
|
||||
bidding_finished: 已完成
|
@ -0,0 +1,28 @@
|
||||
class CreateProjectPackages < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :project_packages do |t|
|
||||
t.references :creator
|
||||
t.string :category
|
||||
t.string :status
|
||||
|
||||
t.string :title
|
||||
t.text :content
|
||||
t.string :contact_name
|
||||
t.string :contact_phone
|
||||
|
||||
t.decimal :min_price
|
||||
t.decimal :max_price
|
||||
|
||||
t.integer :visit_count, default: 0
|
||||
t.integer :bidding_users_count, default: 0
|
||||
|
||||
t.datetime :deadline_at
|
||||
t.datetime :published_at
|
||||
t.datetime :bidding_finished_at
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :project_packages, :published_at
|
||||
end
|
||||
end
|
@ -0,0 +1,12 @@
|
||||
class CreateBiddingUsers < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :bidding_users do |t|
|
||||
t.references :project_package
|
||||
t.references :user
|
||||
|
||||
t.string :status
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,14 @@
|
||||
class CreateProjectPackageApplies < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :project_package_applies do |t|
|
||||
t.references :project_package
|
||||
|
||||
t.string :status
|
||||
t.string :reason
|
||||
|
||||
t.datetime :refused_at
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,8 @@
|
||||
class AddIndexToProjectPackages < ActiveRecord::Migration
|
||||
def change
|
||||
add_index :project_packages, :creator_id
|
||||
add_index :project_package_applies, :project_package_id
|
||||
add_index :bidding_users, :project_package_id
|
||||
add_index :bidding_users, :user_id
|
||||
end
|
||||
end
|
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 501 KiB |
@ -0,0 +1,46 @@
|
||||
import moment from "moment";
|
||||
|
||||
// 处理整点 半点
|
||||
// 取传入时间往后的第一个半点
|
||||
export function handleDateString(dateString) {
|
||||
if (!dateString) return dateString;
|
||||
const ar = dateString.split(':')
|
||||
if (ar[1] == '00' || ar[1] == '30') {
|
||||
return dateString
|
||||
}
|
||||
const miniute = parseInt(ar[1]);
|
||||
if (miniute < 30 || miniute == 60) {
|
||||
return [ar[0], '30'].join(':')
|
||||
}
|
||||
if (miniute < 60) {
|
||||
// 加一个小时
|
||||
const tempStr = [ar[0], '00'].join(':');
|
||||
const format = "YYYY-MM-DD HH:mm:ss";
|
||||
const _moment = moment(tempStr, format)
|
||||
_moment.add(1, 'hours')
|
||||
return _moment.format(format)
|
||||
}
|
||||
|
||||
return dateString
|
||||
}
|
||||
|
||||
// 给moment对象取下一个半点或整点
|
||||
export function getNextHalfHourOfMoment(moment) {
|
||||
if (!moment) {
|
||||
return moment
|
||||
}
|
||||
const minutes = moment.minutes()
|
||||
if (minutes < 30) {
|
||||
moment.minutes(30)
|
||||
} else if (minutes < 60) {
|
||||
moment.minutes(0).add(1, 'hours')
|
||||
}
|
||||
return moment
|
||||
}
|
||||
|
||||
export function formatDuring(mss){
|
||||
var days = parseInt(mss / (1000 * 60 * 60 * 24));
|
||||
var hours = parseInt((mss % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
var minutes = parseInt((mss % (1000 * 60 * 60)) / (1000 * 60));
|
||||
return days + "天" + hours + "小时" + minutes + "分";
|
||||
}
|
@ -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];
|
||||
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];
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Link} from "react-router-dom";
|
||||
|
||||
class PackageBanner extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="project_packagesHead" style={{height: '300px'}}></div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default PackageBanner;
|
||||
|
@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
|
||||
|
||||
//业务组件
|
||||
import PackageBanner from "./PackageBanner";
|
||||
import PackageConcent from "./PackageConcent";
|
||||
|
||||
class PackageIndex extends Component{
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="clearfix">
|
||||
{/*头部banner*/}
|
||||
<PackageBanner {...this.props}></PackageBanner>
|
||||
{/*内容banner*/}
|
||||
<PackageConcent {...this.props}></PackageConcent>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default PackageIndex;
|
@ -0,0 +1,41 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Spin, Icon , Modal,Input,Button} from 'antd';
|
||||
class NEITaskDetailsModel extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return(
|
||||
<Modal
|
||||
keyboard={false}
|
||||
title="提示"
|
||||
visible={this.props.applytype===undefined?false:this.props.applytype}
|
||||
closable={false}
|
||||
footer={null}
|
||||
destroyOnClose={true}
|
||||
centered={true}
|
||||
width="530px"
|
||||
>
|
||||
<div className="task-popup-content">
|
||||
<p className="task-popup-text-center font-16 mb20">
|
||||
<div>{this.props.applyvalue}</div>
|
||||
<div>{this.props.applybottom}</div>
|
||||
</p>
|
||||
<div className="clearfix mt30 edu-txt-center">
|
||||
<a className="task-btn mr30 color_white" onClick={this.props.applycancel}>取消</a>
|
||||
<a className="task-btn task-btn-orange " onClick={this.props.applyconfirm}>确定</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default NEITaskDetailsModel;
|
After Width: | Height: | Size: 305 B |
After Width: | Height: | Size: 281 B |
After Width: | Height: | Size: 341 B |
After Width: | Height: | Size: 288 B |
@ -0,0 +1,53 @@
|
||||
.ysldivhome2{
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-content:stretch;
|
||||
flex-direction: row;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.ysllogin_register_contents{
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
/*justify-content: center;*/
|
||||
background: #fff;
|
||||
|
||||
}
|
||||
.ysldivhomediv1{
|
||||
width: 80px;
|
||||
height: 130px;
|
||||
display: flex;
|
||||
flex-direction:column;
|
||||
margin-left: 48px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.yslgouxuanimg{
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-left: 64px;
|
||||
|
||||
}
|
||||
.yslgouxuanimg2{
|
||||
height: 20px;
|
||||
}
|
||||
.div1img{
|
||||
display: flex;
|
||||
justify-content:center;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius:50%;
|
||||
|
||||
}
|
||||
.textall{
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #4B4B4B;
|
||||
|
||||
}
|
||||
.ptext{
|
||||
width: 80px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
|
||||
}
|
||||
.ysllogin_section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Link} from "react-router-dom";
|
||||
import { Steps, Divider } from 'antd';
|
||||
const { Step } = Steps;
|
||||
|
||||
class PackageIndexNEIBanner extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
current:0
|
||||
}
|
||||
}
|
||||
componentDidMount() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
onChange=(current)=>{
|
||||
debugger
|
||||
console.log('onChange:', current);
|
||||
this.setState({ current });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { current } = this.state;
|
||||
return (
|
||||
<div className="edu-back-white mb20 PackageIndexNEIBanner">
|
||||
<p className="clearfix padding110">
|
||||
<Steps current={current} >
|
||||
<Step title="发布需求"/>
|
||||
<Step title="筛选合适的接包方"/>
|
||||
<Step title="线下交易,完成实施"/>
|
||||
</Steps>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default PackageIndexNEIBanner;
|
||||
|
@ -0,0 +1,60 @@
|
||||
import React, { Component } from 'react';
|
||||
import {BrowserRouter as Router,Route,Switch} from 'react-router-dom';
|
||||
|
||||
//业务组件
|
||||
import PackageIndexNEIBanner from "./PackageIndexNEIBanner";
|
||||
|
||||
import PackageIndexNEIBannerConcent from "./PackageIndexNEIBannerConcent"
|
||||
|
||||
import PackageIndexNEISubmit from './PackageIndexNEISubmit'
|
||||
import '../packageconcnet.css';
|
||||
class PackageIndexNewandEditIndex extends Component{
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
setPublication:false,
|
||||
id:undefined
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
}
|
||||
setPublicationfun=(ids)=>{
|
||||
this.setState({
|
||||
setPublication:true,
|
||||
id:ids
|
||||
})
|
||||
}
|
||||
render() {
|
||||
let {setPublication}=this.state;
|
||||
return (
|
||||
<div>
|
||||
<div className="clearfix">
|
||||
{setPublication===false?<div className={"educontent mt20 mb50"}>
|
||||
|
||||
<p className="clearfix mt20 mb20">
|
||||
<span className="fl font-24 color-grey-3">
|
||||
{this.props.match.params.id!=undefined?"编辑":"新建"}</span>
|
||||
</p>
|
||||
|
||||
<PackageIndexNEIBanner {...this.props} />
|
||||
|
||||
<PackageIndexNEIBannerConcent
|
||||
{...this.props}
|
||||
setPublicationfun={this.setPublicationfun}
|
||||
/>
|
||||
|
||||
</div>:
|
||||
<div className={"educontent mt30 mb50"}>
|
||||
<PackageIndexNEISubmit
|
||||
{...this.props}
|
||||
id={this.state.id}
|
||||
/>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default PackageIndexNewandEditIndex;
|
@ -0,0 +1,72 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Redirect } from 'react-router';
|
||||
|
||||
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
|
||||
|
||||
import Loading from '../../Loading'
|
||||
|
||||
import Loadable from 'react-loadable';
|
||||
import { TPMIndexHOC } from '../tpm/TPMIndexHOC'
|
||||
import { SnackbarHOC } from 'educoder'
|
||||
|
||||
|
||||
const PackageIndex = Loadable({
|
||||
loader: () => import('./PackageIndex/PackageIndex'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
const PackageIndexNewandEdit = Loadable({
|
||||
loader: () => import('./PackageIndexNewandEdit/PackageIndexNewandEditIndex'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
const PackageIndexNEITaskDetails = Loadable({
|
||||
loader: () => import('./PackageIndexNEITaskDetails/PackageIndexNEITaskDetails'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
class ProjectPackageIndex extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div className="newMain clearfix">
|
||||
|
||||
<Switch>
|
||||
{/*众包首页*/}
|
||||
|
||||
<Route path="/project_packages/:id/edit"
|
||||
render={
|
||||
(props) => (<PackageIndexNewandEdit {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
></Route>
|
||||
|
||||
<Route path="/project_packages/new"
|
||||
render={
|
||||
(props) => (<PackageIndexNewandEdit {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
></Route>
|
||||
|
||||
<Route path="/project_packages/:id"
|
||||
render={
|
||||
(props) => (<PackageIndexNEITaskDetails {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
></Route>
|
||||
|
||||
<Route path="/project_packages"
|
||||
render={
|
||||
(props) => (<PackageIndex {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
></Route>
|
||||
</Switch>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SnackbarHOC() (TPMIndexHOC (ProjectPackageIndex)) ;
|
@ -0,0 +1,302 @@
|
||||
.width1240{
|
||||
width:1240px;
|
||||
}
|
||||
.packinput .ant-input{
|
||||
height: 50px;
|
||||
width:749px;
|
||||
border-color: #E1EDF8 !important;
|
||||
}
|
||||
|
||||
.packinput .ant-input-group-addon .ant-btn{
|
||||
width:140px !important;
|
||||
font-size: 18px;
|
||||
height: 50px;
|
||||
background:rgba(76,172,255,1);
|
||||
margin-right:18px;
|
||||
}
|
||||
|
||||
.setissues{
|
||||
width:280px;
|
||||
height:50px;
|
||||
background:rgba(76,172,255,1);
|
||||
border-radius:4px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.pagetype li{
|
||||
color:#8F8F8F !important;
|
||||
}
|
||||
|
||||
.maxwidth700{
|
||||
max-width: 700px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mbf10{
|
||||
margin-bottom:-10px;
|
||||
}
|
||||
|
||||
.PackageIndexNEIBanner{
|
||||
width:1200px;
|
||||
height:110px;
|
||||
background:rgba(255,255,255,1);
|
||||
box-shadow:0px 2px 6px 0px rgba(125,125,125,0.26);
|
||||
border-radius:8px;
|
||||
}
|
||||
|
||||
.padding110{
|
||||
padding: 39px 110px 0px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.borderccc{
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.input-100-40s{
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.fafafas{
|
||||
background-color: #fafafa!important;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.fafafas:focus{
|
||||
background-color: #fff!important;
|
||||
}
|
||||
|
||||
.fafas .ant-input{
|
||||
background-color: #fafafa!important;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.fafas .ant-input:focus{
|
||||
background-color: #fff!important;
|
||||
}
|
||||
.fafas .ant-input-group-addon .ant-btn{
|
||||
width:140px !important;
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
background:rgba(76,172,255,1);
|
||||
}
|
||||
|
||||
.newFormbox .upload_filename{
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
.newFormbox .attachment span{
|
||||
line-height: 23px;
|
||||
}
|
||||
|
||||
.newFormbox .attachment .remove-upload{
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.pd30a0{
|
||||
padding: 30px 30px 0 30px;
|
||||
}
|
||||
|
||||
.newFormbox .attachment .icon-fujian{
|
||||
font-size: 14px !important;
|
||||
line-height: 14px;
|
||||
margin-top: 9px;
|
||||
}
|
||||
|
||||
.newFormbox{
|
||||
height:20px
|
||||
}
|
||||
|
||||
.ml24{
|
||||
margin-left:24px;
|
||||
}
|
||||
|
||||
.defalutCancelbtns{
|
||||
display: block;
|
||||
border: 1px solid #4CACFF !important;
|
||||
background-color: #fff;
|
||||
color: #4CACFF !important;
|
||||
width:130px;
|
||||
height:40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.defalutSubmitbtns{
|
||||
background-color: #4CACFF;
|
||||
height:40px;
|
||||
}
|
||||
|
||||
.defalutSubmitbtnmodels{
|
||||
width:127px;
|
||||
height:30px;
|
||||
background-color: #4CACFF;
|
||||
}
|
||||
|
||||
.ant-steps-item-process .ant-steps-item-icon{
|
||||
background-color: #4CACFF !important;
|
||||
}
|
||||
|
||||
.ant-steps-item-process .ant-steps-item-icon{
|
||||
background-color: #4CACFF !important;
|
||||
}
|
||||
|
||||
.padding200{
|
||||
padding: 115px 200px 215px 200px;
|
||||
}
|
||||
|
||||
.fontcircle{
|
||||
font-size: 80px;
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.sumbtongs{
|
||||
font-size: 24px;
|
||||
display: inherit;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.terraces{
|
||||
font-size: 16px;
|
||||
display: inherit;
|
||||
text-align: center;
|
||||
color:#999;
|
||||
}
|
||||
.padding251{
|
||||
padding: 0px 251px;
|
||||
}
|
||||
|
||||
.ant-modal-title{
|
||||
text-align: center;
|
||||
}
|
||||
.ml17{
|
||||
margin-left: 17px;
|
||||
}
|
||||
|
||||
.project-package-items{
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin:0px !important;
|
||||
padding: 20px;
|
||||
background: white;
|
||||
margin-bottom:0px !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.mtf7{
|
||||
margin-top:-7px;
|
||||
}
|
||||
|
||||
.publicpart.orangeGreen {
|
||||
border-left: 80px solid #29BD8B;
|
||||
}
|
||||
|
||||
.publicwords{
|
||||
left: 3px;
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
.project-packages-list .project-package-items .item-image{
|
||||
width:100px !important;
|
||||
}
|
||||
|
||||
.height185{
|
||||
height: 185px;
|
||||
}
|
||||
|
||||
.ContacttheTA{
|
||||
width: 80px;
|
||||
height: 26px;
|
||||
font-size: 14px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
border: 1px solid #4CACFF !important;
|
||||
background-color: #fff;
|
||||
color: #4CACFF !important;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.ContacttheTAs{
|
||||
width: 80px;
|
||||
height: 26px;
|
||||
font-size: 14px;
|
||||
line-height: 26px;
|
||||
/*display: block;*/
|
||||
border: 1px solid #fff !important;
|
||||
background-color: #4CACFF;
|
||||
color: #fff !important;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.ml28{
|
||||
margin-left: 28px;
|
||||
}
|
||||
|
||||
.longboxs{
|
||||
font-size: 16px;
|
||||
font-family: MicrosoftYaHei-Bold;
|
||||
font-weight: bold;
|
||||
color: rgba(5,16,26,1);
|
||||
border-left: 4px solid rgba(76,172,255,1);
|
||||
padding-left: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.padding020{
|
||||
padding: 0px 20px 20px;
|
||||
}
|
||||
|
||||
.mtf3{
|
||||
margin-top:-3px;
|
||||
}
|
||||
|
||||
.task-btn-nebules{
|
||||
background: #fff!important;
|
||||
color: #4CACFF!important;
|
||||
border: 1px solid #4CACFF!important;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 12px;
|
||||
letter-spacing: 1px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.packageabsolute{
|
||||
position: absolute;
|
||||
right: -16px;
|
||||
top: -11px;
|
||||
}
|
||||
.relativef{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.homehove:hover .ptext{
|
||||
color: #4CACFF!important;
|
||||
}
|
||||
|
||||
.homehove:hover .ContacttheTAs{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.topsj{
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
}
|
||||
.bottomsj{
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
}
|
||||
.touchSelect .ant-spin-dot-spin{
|
||||
margin-top: 30% !important;
|
||||
}
|