diff --git a/app/controllers/projects/base_controller.rb b/app/controllers/projects/base_controller.rb
new file mode 100644
index 000000000..d874b4759
--- /dev/null
+++ b/app/controllers/projects/base_controller.rb
@@ -0,0 +1,5 @@
+class Projects::BaseController < ApplicationController
+ include PaginateHelper
+
+ before_action :require_login, :check_auth
+end
diff --git a/app/controllers/projects/project_applies_controller.rb b/app/controllers/projects/project_applies_controller.rb
new file mode 100644
index 000000000..37d9d615e
--- /dev/null
+++ b/app/controllers/projects/project_applies_controller.rb
@@ -0,0 +1,14 @@
+class Projects::ProjectAppliesController < Projects::BaseController
+ def create
+ project = Projects::ApplyJoinService.call(current_user, create_params)
+ render_ok(project_id: project.id)
+ rescue Projects::ApplyJoinService::Error => ex
+ render_error(ex.message)
+ end
+
+ private
+
+ def create_params
+ params.permit(:code, :role)
+ end
+end
\ No newline at end of file
diff --git a/app/jobs/apply_join_project_notify_job.rb b/app/jobs/apply_join_project_notify_job.rb
new file mode 100644
index 000000000..fe46bf0e0
--- /dev/null
+++ b/app/jobs/apply_join_project_notify_job.rb
@@ -0,0 +1,31 @@
+# 申请成为 管理员、开发者 加入项目 消息通知
+class ApplyJoinProjectNotifyJob < ApplicationJob
+ queue_as :notify
+
+ def perform(user_id, project_id, role)
+ user = User.find_by(id: user_id)
+ project = Project.find_by(id: project_id)
+ return if user.blank? || project.blank?
+
+ attrs = %i[user_id trigger_user_id container_id container_type status
+ belong_container_id belong_container_type tiding_type extra created_at updated_at]
+
+ same_attrs = {
+ trigger_user_id: user.id, status: 0, tiding_type: 'Apply', extra: role,
+ container_id: project.id, container_type: 'JoinProject',
+ belong_container_id: project.id, belong_container_type: 'Project'
+ }
+
+ # 报告人员加入时消息为系统通知消息
+ if role == 5
+ same_attrs[:container_type] = 'ReporterJoinProject'
+ same_attrs[:tiding_type] = 'System'
+ end
+
+ Tiding.bulk_insert(*attrs) do |worker|
+ project.manager_members.each do |manager|
+ worker.add(same_attrs.merge(user_id: manager.user_id))
+ end
+ end
+ end
+end
diff --git a/app/models/applied_project.rb b/app/models/applied_project.rb
new file mode 100644
index 000000000..901443e81
--- /dev/null
+++ b/app/models/applied_project.rb
@@ -0,0 +1,9 @@
+class AppliedProject < ApplicationRecord
+ belongs_to :user
+ belongs_to :project
+
+ has_many :applied_messages, as: :applied, dependent: :destroy
+ has_many :forge_activities, as: :forge_act, dependent: :destroy
+
+ scope :pending, -> { where(status: 0) }
+end
diff --git a/app/models/forge_activity.rb b/app/models/forge_activity.rb
new file mode 100644
index 000000000..77103d0ff
--- /dev/null
+++ b/app/models/forge_activity.rb
@@ -0,0 +1,5 @@
+class ForgeActivity < ApplicationRecord
+ belongs_to :user
+ belongs_to :project
+ belongs_to :forge_act, polymorphic: true
+end
\ No newline at end of file
diff --git a/app/models/member.rb b/app/models/member.rb
index d1feb8a37..70b7fe305 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -1,6 +1,8 @@
class Member < ApplicationRecord
- has_many :member_roles, dependent: :destroy
+ belongs_to :user
belongs_to :course, optional: true
belongs_to :project, optional: true
- belongs_to :user
+
+ has_many :member_roles, dependent: :destroy
+ has_many :roles, through: :member_roles
end
diff --git a/app/models/member_role.rb b/app/models/member_role.rb
index 900efc732..2461c52f1 100644
--- a/app/models/member_role.rb
+++ b/app/models/member_role.rb
@@ -1,3 +1,4 @@
class MemberRole < ApplicationRecord
+ belongs_to :role
belongs_to :member
end
diff --git a/app/models/project.rb b/app/models/project.rb
index ddc6f6e5f..c3c626cb0 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1,9 +1,12 @@
class Project < ApplicationRecord
belongs_to :owner, class_name: 'User', foreign_key: :user_id
+
has_many :members
+ has_many :manager_members, -> { joins(:roles).where(roles: { name: 'Manager' }) }, class_name: 'Member'
has_one :project_score, dependent: :destroy
has_many :issues
+ has_many :user_grades, dependent: :destroy
# 创建者
def creator
diff --git a/app/models/role.rb b/app/models/role.rb
new file mode 100644
index 000000000..e60606ffa
--- /dev/null
+++ b/app/models/role.rb
@@ -0,0 +1,3 @@
+class Role < ApplicationRecord
+ has_many :member_roles, dependent: :destroy
+end
\ No newline at end of file
diff --git a/app/models/user.rb b/app/models/user.rb
index a95776be9..14d7b2697 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -128,6 +128,9 @@ class User < ApplicationRecord
has_many :bidding_users, dependent: :destroy
has_many :bidden_project_packages, through: :bidding_users, source: :project_package
+ # 项目
+ has_many :applied_projects, dependent: :destroy
+
# Groups and active users
scope :active, lambda { where(status: STATUS_ACTIVE) }
diff --git a/app/models/user_grade.rb b/app/models/user_grade.rb
new file mode 100644
index 000000000..dffbb4743
--- /dev/null
+++ b/app/models/user_grade.rb
@@ -0,0 +1,4 @@
+class UserGrade < ApplicationRecord
+ belongs_to :project
+ belongs_to :user
+end
diff --git a/app/services/projects/apply_join_service.rb b/app/services/projects/apply_join_service.rb
new file mode 100644
index 000000000..a177de930
--- /dev/null
+++ b/app/services/projects/apply_join_service.rb
@@ -0,0 +1,82 @@
+class Projects::ApplyJoinService < ApplicationService
+ Error = Class.new(StandardError)
+
+ attr_reader :user, :params
+
+ def initialize(user, params)
+ @user = user
+ @params = params
+ end
+
+ def call
+ validate!
+
+ # 项目报告人员直接加入项目
+ if params[:role] == 'reporter'
+ Projects::JoinService.call(project, user, role: 'reporter')
+ return project
+ end
+
+ ActiveRecord::Base.transaction do
+ apply = user.applied_projects.create!(project: project, role: role_value)
+
+ apply.forge_activities.find_or_create_by!(user: user, project: project)
+
+ notify_project_manager!
+ end
+
+ # notify_project_owner
+ ApplyJoinProjectNotifyJob.perform_later(user.id, project.id, role_value)
+
+ project
+ end
+
+ private
+
+ def project
+ @_project ||= Project.find_by(invite_code: params[:code].to_s.strip)
+ end
+
+ def role_value
+ @_role ||=
+ case params[:role]
+ when 'manager' then 3
+ when 'developer' then 4
+ when 'reporter' then 5
+ else raise Error, '角色无效'
+ end
+ end
+
+ def notify_project_manager!
+ columns = %i[user_id applied_id applied_type status viewed applied_user_id role project_id created_at updated_at]
+ AppliedMessage.bulk_insert(*columns) do |worker|
+ base_attr = { status: false, viewed: false, applied_user_id: user.id, role: role_value, project_id: project.id }
+
+ project.manager_members.each do |manager|
+ worker.add(base_attr.merge(user_id: manager.user_id))
+ end
+ end
+ end
+
+ def notify_project_owner
+ owner = project.user
+ return if owner.phone.blank?
+
+ Educoder::Sms.send(mobile: owner.phone, send_type:'applied_project_info',
+ user_name: owner.show_name, name: project.name)
+ rescue Exception => ex
+ Rails.logger.error("发送短信失败 => #{ex.message}")
+ end
+
+ def validate!
+ # params check
+ raise Error, '邀请码不能为空' if params[:code].blank?
+ raise Error, '角色不能为空' if params[:role].blank?
+ raise Error, '角色无效' unless %w(manager developer reporter).include?(params[:role])
+
+ # logical check
+ raise Error, '邀请码无效' if project.blank?
+ raise Error, '您已在该项目中' if project.member?(user)
+ raise Error, '您已经提交过申请' if user.applied_projects.pending.exists?(project: project)
+ end
+end
\ No newline at end of file
diff --git a/app/services/projects/join_service.rb b/app/services/projects/join_service.rb
new file mode 100644
index 000000000..b434e48cd
--- /dev/null
+++ b/app/services/projects/join_service.rb
@@ -0,0 +1,35 @@
+class Projects::JoinService < ApplicationService
+ attr_reader :project, :user, :opts
+
+ def initialize(project, user, **opts)
+ @project = project
+ @user = user
+ @opts = opts
+ end
+
+ def call
+ ActiveRecord::Base.transaction do
+ member = project.members.create!(user: user)
+
+ member.member_roles.create!(role_id: role_value)
+
+ project.user_grades.find_or_create_by!(user: user)
+ end
+
+ ApplyJoinProjectNotifyJob.perform_later(user, project, role_value)
+
+ project
+ end
+
+ private
+
+ def role_value
+ @_role ||=
+ case opts[:role]
+ when 'manager' then 3
+ when 'developer' then 4
+ when 'reporter' then 5
+ else raise ArgumentError
+ end
+ end
+end
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 6024201a4..6623edd99 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -695,6 +695,10 @@ Rails.application.routes.draw do
end
resources :libraries, only: [:index, :show, :create, :update, :destroy]
+
+ scope module: :projects do
+ resources :applied_projects, only: [:create]
+ end
end
#git 认证回调
diff --git a/public/react/public/css/edu-all.css b/public/react/public/css/edu-all.css
index f58d44978..d220ba3a0 100644
--- a/public/react/public/css/edu-all.css
+++ b/public/react/public/css/edu-all.css
@@ -15,7 +15,7 @@
text-align: center;
height: 60px;
box-sizing: border-box;
- min-width: 1200px;
+ min-width: 950px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
diff --git a/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js b/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js
index a300e73dc..f1b9faab7 100644
--- a/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js
+++ b/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js
@@ -384,7 +384,7 @@ class CommonWorkDetailIndex extends Component{
{/* 作品列表 */}
()
+ (props) => ()
}
>
diff --git a/public/react/src/modules/courses/busyWork/CommonWorkList.js b/public/react/src/modules/courses/busyWork/CommonWorkList.js
index f9deeebb4..8bc029cec 100644
--- a/public/react/src/modules/courses/busyWork/CommonWorkList.js
+++ b/public/react/src/modules/courses/busyWork/CommonWorkList.js
@@ -400,8 +400,13 @@ class CommonWorkList extends Component{
componentDidMount() {
this.fetchList()
on('commonwork_fetch_all', this.fetchAllListener)
- $("html").animate({ scrollTop: $('html').scrollTop() - 100 })
- this.props.triggerRef(this);
+ $("html").animate({ scrollTop: $('html').scrollTop() - 100 });
+ try {
+ this.props.triggerRef(this);
+ }catch (e) {
+
+ }
+
}
componentWillUnmount() {
off('commonwork_fetch_all', this.fetchAllListener)
diff --git a/public/react/src/modules/courses/exercise/Exercisetablesmubu.js b/public/react/src/modules/courses/exercise/Exercisetablesmubu.js
index 60c29ffbc..92cfa30b7 100644
--- a/public/react/src/modules/courses/exercise/Exercisetablesmubu.js
+++ b/public/react/src/modules/courses/exercise/Exercisetablesmubu.js
@@ -52,7 +52,7 @@ class Exercisetablesmubus extends Component {
dataIndex: 'commit_percent',
key: 'commit_percent',
render: (text, record, index) => {
- const _content =
+ const _content =
{text.value!="有效填写量"&&text.value!="wrong" &&
}
diff --git a/public/react/src/modules/user/LoginRegisterComponent.js b/public/react/src/modules/user/LoginRegisterComponent.js
index 72614e566..99884e28e 100644
--- a/public/react/src/modules/user/LoginRegisterComponent.js
+++ b/public/react/src/modules/user/LoginRegisterComponent.js
@@ -678,63 +678,6 @@ class LoginRegisterComponent extends Component {
}
//失去焦点判断
inputOnBlur = (e, id) => {
- // debugger
- // // this.isCorrectname(e.target.value, id);
- // // this.Emailphonenumberverification(e.target.value, id);
- // if (e.target.value.length === 0) {
- // this.setState({
- // Phonenumberisnotco: undefined,
- // Phonenumberisnotcobool: false,
- // })
- // return;
- // }
- // // var telephone = $("#telephoneAdd.tianjia_phone").val();
- // var regph = /^[1][3,4,5,6,7,8][0-9]{9}$/;
- // // var email = $("#add_email.tianjia_email").val();
- // var regemail = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;
- //
- // // [1]手机号开头必须是1 [3,4,5,6,7,8] 第二位是3-8中的一个 [0-9]{9} 后边9位可以是0-9的任意数字。
- // var stringdata = undefined;
- // if (!regph.test(e.target.value)) {
- // stringdata = "手机号格式不正确";
- // this.setState({
- // Phonenumberisnotco: stringdata,
- // Phonenumberisnotcobool: true,
- // dragOk:false,
- // Whethertoverify:this.state.Whethertoverify===true?false:true,
- // })
- // } else {
- // console.log("706");
- // this.setState({
- // Phonenumberisnotco: undefined,
- // Phonenumberisnotcobool: false,
- // })
- // return
- // }
- //
- // if (!regemail.test(e.target.value)) {
- // if ((e.target.value.indexOf("@") != -1) === true) {
- // stringdata = "邮箱格式不正确";
- // } else {
- // stringdata = "手机号格式不正确";
- //
- // }
- // this.setState({
- // Phonenumberisnotco: stringdata,
- // Phonenumberisnotcobool: true,
- // dragOk:false,
- // Whethertoverify:this.state.Whethertoverify===true?false:true,
- // })
- // return
- // } else {
- // console.log("729");
- // this.setState({
- // Phonenumberisnotco: undefined,
- // Phonenumberisnotcobool: false,
- // })
- // this.Emailphonenumberverification(e.target.value, 1);
- // return
- // }
this.Emailphonenumberverification(e.target.value, 1);
}
inputOnBlurzhuche = (e, id) => {
diff --git a/public/stylesheets/educoder/edu-all.css b/public/stylesheets/educoder/edu-all.css
index 6c77ee1c5..61cfb4175 100644
--- a/public/stylesheets/educoder/edu-all.css
+++ b/public/stylesheets/educoder/edu-all.css
@@ -15,7 +15,7 @@
text-align: center;
height: 60px;
box-sizing: border-box;
- min-width: 1200px;
+ min-width: 950px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;