project_pack
杨树明 5 years ago
commit def32478e8

@ -3,6 +3,8 @@
module Mobile module Mobile
module Apis module Apis
class ProjectPackages < Grape::API class ProjectPackages < Grape::API
use Mobile::Middleware::NotFoundHandler
helpers do helpers do
def current_package def current_package
@_current_package ||= ProjectPackage.find(params[:id]) @_current_package ||= ProjectPackage.find(params[:id])
@ -19,6 +21,7 @@ module Mobile
optional :category, type: String, desc: '类型' optional :category, type: String, desc: '类型'
optional :keyword, type: String, desc: '搜索关键字' optional :keyword, type: String, desc: '搜索关键字'
optional :sort_by, type: String, desc: '排序' optional :sort_by, type: String, desc: '排序'
optional :sort_direction, type: String, desc: '排序方向'
optional :page, type: Integer, desc: '页数' optional :page, type: Integer, desc: '页数'
optional :per_page, type: Integer, desc: '分页大小' optional :per_page, type: Integer, desc: '分页大小'
end end
@ -32,11 +35,10 @@ module Mobile
count = packages.count count = packages.count
if params[:sort_by] == 'price' direction = params[:sort_direction] == 'asc' ? 'asc' : 'desc'
packages = packages.order('min_price desc') sort = params[:sort_by] == 'price' ? 'min_price' : 'published_at'
else packages = packages.order("#{sort} #{direction}")
packages = packages.order('published_at desc')
end
packages = paginate packages.includes(:creator, :attachments, bidding_users: :user) packages = paginate packages.includes(:creator, :attachments, bidding_users: :user)
present :count, count present :count, count
@ -51,8 +53,13 @@ module Mobile
route_param :id do route_param :id do
get do get do
authenticate! 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: current_user present current_package, with: Mobile::Entities::ProjectPackage, type: :show, user: user
end end
resource :bidding_users do resource :bidding_users do

@ -32,7 +32,7 @@ module Mobile
{ {
id: package.creator.id, id: package.creator.id,
login: package.creator.login, login: package.creator.login,
name: package.creator.show_real_name, name: package.creator.show_name,
avatar_url: "/images/#{url_to_avatar(package.creator)}" avatar_url: "/images/#{url_to_avatar(package.creator)}"
} }
end end
@ -54,7 +54,7 @@ module Mobile
{ {
id: user.id, id: user.id,
login: user.login, login: user.login,
name: user.name, name: user.show_name,
avatar_url: "/images/#{url_to_avatar(user)}", avatar_url: "/images/#{url_to_avatar(user)}",
status: bidding_user.status status: bidding_user.status
} }

@ -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

@ -25,6 +25,18 @@ class ProjectPackagesController < ApplicationController
render_react render_react
end 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 private
def render_react def render_react
render file: 'public/react/build/index.html', :layout => false render file: 'public/react/build/index.html', :layout => false

@ -2425,6 +2425,8 @@ class UsersController < ApplicationController
when "a_bank", "m_bank", "p_bank" when "a_bank", "m_bank", "p_bank"
@sort = params[:sort] || "updated_at" @sort = params[:sort] || "updated_at"
@p = params[:p] || "Common" @p = params[:p] || "Common"
when 'a_package', 'l_package', 'p_package'
@p = params[:p] || 'a'
end end
case @type case @type
@ -2565,7 +2567,7 @@ class UsersController < ApplicationController
if @show_all if @show_all
status = status =
case params[:q] case params[:p]
when '0' then %w(pending applying refused) when '0' then %w(pending applying refused)
when '1' then %w(published) when '1' then %w(published)
when '2' then %w(bidding_ended bidding_finished) when '2' then %w(bidding_ended bidding_finished)
@ -2578,13 +2580,14 @@ class UsersController < ApplicationController
packages = packages.where(status: %w(published bidding_ended bidding_finished)) packages = packages.where(status: %w(published bidding_ended bidding_finished))
end end
@per_page = 20
ids = bidding_packages.pluck(:id) + packages.pluck(:id) ids = bidding_packages.pluck(:id) + packages.pluck(:id)
@objects = ProjectPackage.where(id: ids).order("published_at #{order}") @objects = ProjectPackage.where(id: ids).order("published_at #{order}")
when 'p_package' when 'p_package'
packages = @user.project_packages packages = @user.project_packages
if @show_all if @show_all
status = status =
case params[:q] case params[:p]
when '0' then %w(pending applying refused) when '0' then %w(pending applying refused)
when '1' then %w(published) when '1' then %w(published)
when '2' then %w(bidding_ended bidding_finished) when '2' then %w(bidding_ended bidding_finished)
@ -2595,18 +2598,20 @@ class UsersController < ApplicationController
else else
packages = packages.where(status: %w(published bidding_ended bidding_finished)) packages = packages.where(status: %w(published bidding_ended bidding_finished))
end end
@per_page = 20
@objects = packages.order("published_at #{order}") @objects = packages.order("published_at #{order}")
when 'l_package' when 'l_package'
packages = @user.bidding_project_packages packages = @user.bidding_project_packages
if @show_all if @show_all
status = status =
case params[:q] case params[:p]
when '0' then %w(bidding_lost) when '0' then %w(bidding_lost)
when '1' then %w(bidding_won) when '1' then %w(bidding_won)
end end
packages = packages.where(bidding_users: { status: status }) if status.present? packages = packages.where(bidding_users: { status: status }) if status.present?
end end
@per_page = 20
@objects = packages.order("published_at #{order}") @objects = packages.order("published_at #{order}")
end end
@objects_count = @objects.size @objects_count = @objects.size
@ -2625,7 +2630,7 @@ class UsersController < ApplicationController
@objects = @objects.to_a @objects = @objects.to_a
@objects.unshift("new") if @new_icon @objects.unshift("new") if @new_icon
@objects = paginateHelper @objects, 16 @objects = paginateHelper @objects, @per_page || 16
respond_to do |format| respond_to do |format|
format.js format.js

@ -56,10 +56,18 @@ class ProjectPackage < ActiveRecord::Base
end end
end end
def visitable?
!editable?
end
def editable? def editable?
pending? || applying? || refused? pending? || applying? || refused?
end end
def deletable?
pending? || refused?
end
def bidding_end? def bidding_end?
flag = deadline_at < Time.now flag = deadline_at < Time.now
end_bidding! if flag && can_end_bidding? end_bidding! if flag && can_end_bidding?
@ -73,4 +81,8 @@ class ProjectPackage < ActiveRecord::Base
def increment_visit_count! def increment_visit_count!
ProjectPackage.connection.execute("update project_packages set visit_count = COALESCE(visit_count, 0) + 1 where id = #{id}") ProjectPackage.connection.execute("update project_packages set visit_count = COALESCE(visit_count, 0) + 1 where id = #{id}")
end end
def category_text
I18n.t("project_package.category.#{category}")
end
end end

@ -10,7 +10,7 @@ class ProjectPackages::ApplyPublishService
def call def call
return if package.applying? return if package.applying?
raise Error, '该状态下不能申请发布' if package.can_apply? raise Error, '该状态下不能申请发布' unless package.may_apply?
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
package.apply! package.apply!

@ -21,6 +21,6 @@ class ProjectPackages::EndBiddingService
end end
def package_deadline? def package_deadline?
package.deadline_at < Time.now package.may_end_bidding? && package.deadline_at < Time.now
end end
end end

@ -22,10 +22,9 @@ class ProjectPackages::SaveService
send_create_notify! if is_create send_create_notify! if is_create
ProjectPackages::ApplyPublishService.call(package) if with_publish? ProjectPackages::ApplyPublishService.new(package).call if with_publish?
end end
package package
rescue ProjectPackages::ApplyPublishService::Error => ex rescue ProjectPackages::ApplyPublishService::Error => ex
raise Error, ex.message raise Error, ex.message
@ -52,7 +51,9 @@ class ProjectPackages::SaveService
package.attachments.where(id: destroy_ids).delete_all package.attachments.where(id: destroy_ids).delete_all
new_ids = attachment_ids - old_attachment_ids new_ids = attachment_ids - old_attachment_ids
Attachment.where(id: new_ids).update_all(container_type: package.id, container_type: 'ProjectPackage') if new_ids.present? if new_ids.present?
Attachment.where(id: new_ids, container_id: nil).update_all(container_id: package.id, container_type: 'ProjectPackage')
end
end end
def send_create_notify! def send_create_notify!

@ -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

@ -1,6 +1,6 @@
<% if @applies.present? %> <% if @applies.present? %>
<% @applies.each do |apply| %> <% @applies.each do |apply| %>
<% user = apply.project_package.user %> <% user = apply.project_package.creator %>
<% project_package = apply.project_package %> <% project_package = apply.project_package %>
<div class="admin-con-box apply-<%= apply.id %> clearfix"> <div class="admin-con-box apply-<%= apply.id %> clearfix">
<a href="<%= user_path(user) %>" target="_blank" class="fl with10 edu-ad-user"> <a href="<%= user_path(user) %>" target="_blank" class="fl with10 edu-ad-user">

@ -23,13 +23,14 @@
<% if @objects_count > 0 %> <% if @objects_count > 0 %>
<div class="educontent project-packages-list"> <div class="educontent project-packages-list">
<% @objects.each do |object| %> <% @objects.each do |object| %>
<div class="project-package-item"> <% 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"> <div class="item-image">
<%= image_tag("educoder/project_packages/#{object.category}.png") %> <%= image_tag("educoder/project_packages/#{object.category}.png") %>
</div> </div>
<div class="item-body"> <div class="item-body">
<div class="item-head"> <div class="item-head">
<div class="item-head-title"> <div class="item-head-title" data-tip-down="<%= object.title %>">
<%= link_to object.title, project_package_path(object) %> <%= link_to object.title, project_package_path(object) %>
</div> </div>
<div class="item-head-tags"> <div class="item-head-tags">
@ -40,14 +41,15 @@
</div> </div>
<div class="item-head-blank"></div> <div class="item-head-blank"></div>
<div class="item-head-price"> <div class="item-head-price">
<span>¥</span><%= object.min_price %>
<% if object.max_price && object.max_price != object.min_price %> <% if object.max_price && object.max_price != object.min_price %>
~<span>¥</span><%= object.max_price %> <span>¥</span><%= object.min_price %>~<span>¥</span><%= object.max_price %>
<% else %>
<span>¥</span><%= object.min_price %>
<% end %> <% end %>
</div> </div>
</div> </div>
<div class="item-category"> <div class="item-category">
<div class="item-category-item">人工智能</div> <div class="item-category-item"><%= object.category_text %></div>
</div> </div>
<div class="item-other"> <div class="item-other">
<div class="item-group item-other-visit"> <div class="item-group item-other-visit">
@ -57,7 +59,13 @@
<div class="item-group item-other-deadline"> <div class="item-group item-other-deadline">
<% if object.published? %> <% if object.published? %>
<span class="item-group-icon"><i class="fa fa-clock-o"></i></span> <span class="item-group-icon"><i class="fa fa-clock-o"></i></span>
<span class="item-group-text">内竞标截止</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 %> <% end %>
</div> </div>
<div class="item-group item-other-bidding"> <div class="item-group item-other-bidding">
@ -74,9 +82,61 @@
</div> </div>
</div> </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> </div>
<% end %> <% end %>
</div> </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 %> <% else %>
<%= render :partial => "welcome/no_data" %> <%= render :partial => "welcome/no_data" %>
<% end %> <% 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: "<%= user_path(@user, params.except(:controller, :action)) %>" });
}
} else {
notice_box(data.message);
}
}
});
return true;
}
</script>

@ -0,0 +1,11 @@
zh:
project_package:
category:
front: 前端开发
backend: 后端开发
mobile: 移动开发
database: 数据库
cloud_compute_and_big_data: 云计算与大数据
devops_and_test: 运维与测试
ai: 人工智能
other: 其它

@ -57,7 +57,7 @@ RedmineApp::Application.routes.draw do ## oauth相关
get :publish_success, on: :collection get :publish_success, on: :collection
end end
resources :project_packages, only: [:index, :show, :new, :edit] do resources :project_packages, only: [:index, :show, :new, :edit, :destroy] do
get :apply_success, on: :member get :apply_success, on: :member
end end

@ -1017,13 +1017,27 @@ html>body #ajax-indicator { position: fixed; }
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
transition: max-width .2s;
-moz-transition: max-width .2s; /* Firefox 4 */
-webkit-transition: max-width .2s; /* Safari 和 Chrome */
-o-transition: max-width .2s; /* Opera */
}
.project-package-item .item-head-tags {
display: -webkit-flex;
display: flex;
align-items: center;
} }
.project-package-item .item-head-tags span { .project-package-item .item-head-tags span {
margin-left: 20px;
padding: 0 10px;
height: 28px;
font-size: 14px;
color: white; color: white;
border-radius: 5px;
} }
.project-package-item .item-head-tags span.pending { color: red; } .project-package-item .item-head-tags span.pending { background: lightcoral; }
.project-package-item .item-head-tags span.bidding_won { color: lightgreen; } .project-package-item .item-head-tags span.bidding_won { background: lightgreen; }
.project-package-item .item-head-tags span.bidding_lost { color: grey; } .project-package-item .item-head-tags span.bidding_lost { background: grey; }
.project-package-item .item-head-blank { .project-package-item .item-head-blank {
flex: 1; flex: 1;
@ -1048,6 +1062,7 @@ html>body #ajax-indicator { position: fixed; }
background:rgba(255,235,213,1); background:rgba(255,235,213,1);
border-radius:13px; border-radius:13px;
} }
.project-package-item .item-other { .project-package-item .item-other {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
@ -1055,8 +1070,39 @@ html>body #ajax-indicator { position: fixed; }
color: #999999; color: #999999;
} }
.project-package-item .item-group { .project-package-item .item-group {
flex: 1; flex: 2;
}
.project-package-item .item-group.item-other-publish-at {
text-align: right;
} }
.project-package-item .item-other-blank { .project-package-item .item-other-blank {
flex: 2 flex: 3
}
.project-package-item.with-operator .item-operator {
width: 0;
transition: width .2s;
-moz-transition: width .2s; /* Firefox 4 */
-webkit-transition: width .2s; /* Safari 和 Chrome */
-o-transition: width .2s; /* Opera */
}
.project-package-item.with-operator:hover .item-operator {
margin: -20px -20px -20px 20px;
padding: 20px 0;
width: 100px;
display: flex;
justify-content: space-around;
flex-direction: column;
align-items: center;
background: #f0f0f0;
}
.project-package-item.with-operator .item-operator a {
display: none;
}
.project-package-item.with-operator:hover .item-operator a {
display: block;
font-size: 20px;
}
.project-package-item.with-operator:hover .item-head-title {
max-width: 600px;
} }
Loading…
Cancel
Save