@ -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 + "分";
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|