diff --git a/app/controllers/concerns/paginate_helper.rb b/app/controllers/concerns/paginate_helper.rb index 34740eb5d..bbe84a348 100644 --- a/app/controllers/concerns/paginate_helper.rb +++ b/app/controllers/concerns/paginate_helper.rb @@ -1,7 +1,7 @@ module PaginateHelper def paginate(objs, **opts) page = params[:page].to_i <= 0 ? 1 : params[:page].to_i - per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : opts[:per_page] || 20 + per_page = params[:per_page].to_i > 0 && params[:per_page].to_i < 50 ? params[:per_page].to_i : opts[:per_page] || 20 Kaminari.paginate_array(objs).page(page).per(per_page) end diff --git a/app/controllers/users/base_controller.rb b/app/controllers/users/base_controller.rb index afc03ee13..fd138a182 100644 --- a/app/controllers/users/base_controller.rb +++ b/app/controllers/users/base_controller.rb @@ -26,9 +26,22 @@ class Users::BaseController < ApplicationController render_forbidden end + def page_value + params[:page].to_i <= 0 ? 1 : params[:page].to_i + end + + def per_page_value + params[:per_page].to_i > 0 && params[:per_page].to_i < 50 ? params[:per_page].to_i : 20 + end + alias_method :limit_value, :per_page_value + + def offset_value + (page_value - 1) * limit_value + end + def paginate(objs, **opts) - page = params[:page].to_i <= 0 ? 1 : params[:page].to_i - per_page = params[:per_page].to_i > 0 ? params[:per_page].to_i : 20 + page = page_value + per_page = per_page_value return Kaminari.paginate_array(objs).page(page).per(per_page) unless observed_logged_user? && opts[:special] diff --git a/app/controllers/users/private_message_details_controller.rb b/app/controllers/users/private_message_details_controller.rb new file mode 100644 index 000000000..486d23d7f --- /dev/null +++ b/app/controllers/users/private_message_details_controller.rb @@ -0,0 +1,23 @@ +class Users::PrivateMessageDetailsController < Users::BaseController + before_action :private_user_resources! + + after_action :update_message_status, only: [:show] + + def show + messages = observed_user.private_messages.without_deleted.where(target: target_user) + + @count = messages.count + @messages = messages.order(send_time: :asc).includes(sender: :user_extension) + end + + private + + def target_user + @_target_user ||= User.find(params[:target_id]) + end + + # 置为已读 + def update_message_status + observed_user.private_messages.only_unread.where(target: target_user).update_all(status: 1) + end +end \ No newline at end of file diff --git a/app/controllers/users/private_messages_controller.rb b/app/controllers/users/private_messages_controller.rb new file mode 100644 index 000000000..b80b6152b --- /dev/null +++ b/app/controllers/users/private_messages_controller.rb @@ -0,0 +1,39 @@ +class Users::PrivateMessagesController < Users::BaseController + before_action :private_user_resources! + after_action :update_onclick_time!, only: [:index] + + def index + @count = observed_user.private_messages.without_deleted.group(:target_id).count.count + + subquery = observed_user.private_messages.without_deleted.order(send_time: :desc).to_sql + query = "SELECT subquery.*, COUNT(*) message_count FROM (#{subquery}) subquery "\ + "GROUP BY subquery.target_id ORDER BY subquery.send_time desc LIMIT #{limit_value} OFFSET #{offset_value}" + @messages = PrivateMessage.select('*').from("(#{query}) AS query").includes(target: :user_extension) + end + + def create + receiver = User.find_by(id: params[:target_id]) + return render_error('用户未找到') if receiver.blank? + + @message = PrivateMessages::CreateService.call(observed_user, receiver, create_params) + rescue PrivateMessages::CreateService::Error => ex + render_error(ex.message) + end + + def destroy + message = observed_user.private_messages.without_deleted.find(params[:id]) + message.destroy! + + render_ok + end + + private + + def update_onclick_time! + current_user.onclick_time.touch(:onclick_time) + end + + def create_params + params.permit(:content) + end +end \ No newline at end of file diff --git a/app/controllers/users/recent_contacts_controller.rb b/app/controllers/users/recent_contacts_controller.rb new file mode 100644 index 000000000..bc4b8ea8f --- /dev/null +++ b/app/controllers/users/recent_contacts_controller.rb @@ -0,0 +1,8 @@ +class Users::RecentContactsController < Users::BaseController + before_action :private_user_resources! + + def index + contacts = observed_user.recent_contacts.distinct + @contacts = contacts.order('private_messages.created_at DESC').limit(10).includes(:user_extension) + end +end \ No newline at end of file diff --git a/app/controllers/users/unread_message_infos_controller.rb b/app/controllers/users/unread_message_infos_controller.rb new file mode 100644 index 000000000..7abd36304 --- /dev/null +++ b/app/controllers/users/unread_message_infos_controller.rb @@ -0,0 +1,12 @@ +class Users::UnreadMessageInfosController < Users::BaseController + before_action :private_user_resources! + + def show + click_time = observed_user.click_time + + unread_tiding_count = observed_user.tidings.where('created_at > ?', click_time).count + unread_message_count = observed_user.private_messages.only_unread.group(:target_id).count.count + + render_ok(unread_tiding_count: unread_tiding_count, unread_message_count: unread_message_count) + end +end \ No newline at end of file diff --git a/app/controllers/users_for_private_messages_controller.rb b/app/controllers/users_for_private_messages_controller.rb new file mode 100644 index 000000000..bbd5682a1 --- /dev/null +++ b/app/controllers/users_for_private_messages_controller.rb @@ -0,0 +1,17 @@ +class UsersForPrivateMessagesController < ApplicationController + before_action :require_login, :check_auth + + def index + users = User.active.where.not(id: current_user.id) + + keyword = params[:keyword].to_s.strip + if keyword.blank? + @users = [] + return + end + + users = users.where('LOWER(concat(lastname, firstname, nickname)) LIKE ?', "%#{keyword}%") + + @users = users.limit(10).includes(:user_extension) + end +end \ No newline at end of file diff --git a/app/decorators/private_message_decorator.rb b/app/decorators/private_message_decorator.rb new file mode 100644 index 000000000..6db17acf3 --- /dev/null +++ b/app/decorators/private_message_decorator.rb @@ -0,0 +1,9 @@ +module PrivateMessageDecorator + extend ApplicationDecorator + + display_time_method :send_time + + def unread? + status.zero? + end +end \ No newline at end of file diff --git a/app/models/private_message.rb b/app/models/private_message.rb index 1db4c9f66..640e48db7 100644 --- a/app/models/private_message.rb +++ b/app/models/private_message.rb @@ -1,3 +1,9 @@ class PrivateMessage < ApplicationRecord belongs_to :user + belongs_to :target, class_name: "User" + belongs_to :sender, class_name: "User" + belongs_to :receiver, class_name: "User" + + scope :without_deleted, -> { where.not(status: 2) } + scope :only_unread, -> { where(status: 0) } end diff --git a/app/models/user.rb b/app/models/user.rb index 14d7b2697..ed9d70c00 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -54,7 +54,8 @@ class User < ApplicationRecord has_one :onclick_time, :dependent => :destroy # 新版私信 - has_many :private_messages, :dependent => :destroy + has_many :private_messages, dependent: :destroy + has_many :recent_contacts, through: :private_messages, source: :target has_many :tidings, :dependent => :destroy has_many :games, :dependent => :destroy diff --git a/app/services/private_messages/create_service.rb b/app/services/private_messages/create_service.rb new file mode 100644 index 000000000..560f1a540 --- /dev/null +++ b/app/services/private_messages/create_service.rb @@ -0,0 +1,35 @@ +class PrivateMessages::CreateService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :sender, :receiver, :params + + def initialize(sender, receiver, **params) + @sender = sender + @receiver = receiver + @params = params + end + + def call + validate! + + same_attr = { sender: sender, receiver: receiver, content: content, send_time: Time.now } + + message = nil + ActiveRecord::Base.transaction do + message = sender.private_messages.create!(same_attr.merge(target: receiver, status: 1)) + receiver.private_messages.create!(same_attr.merge(target: sender, status: 0)) + end + message + end + + private + + def content + @_content ||= params[:content].to_s.strip + end + + def validate! + raise Error, '内容不能为空' if content.blank? + raise Error, '内容太长' if content.size > 255 + end +end \ No newline at end of file diff --git a/app/services/project_packages/save_service.rb b/app/services/project_packages/save_service.rb index bcfc19a10..caf4cf6c7 100644 --- a/app/services/project_packages/save_service.rb +++ b/app/services/project_packages/save_service.rb @@ -15,7 +15,7 @@ class ProjectPackages::SaveService < ApplicationService is_create = package.new_record? raise Error, '类型不存在' unless ProjectPackageCategory.where(id: params[:category_id]).exists? - params[:project_package_category_id] = params[:category_id].to_i + params[:project_package_category_id] = params.delete(:category_id).to_i raise Error, '竞标截止时间不能小于当前时间' if params[:deadline_at].present? && params[:deadline_at].to_time < Time.now diff --git a/app/views/users/get_user_info.json.jbuilder b/app/views/users/get_user_info.json.jbuilder index d9eb736ad..d12b950f9 100644 --- a/app/views/users/get_user_info.json.jbuilder +++ b/app/views/users/get_user_info.json.jbuilder @@ -7,6 +7,7 @@ json.is_teacher @user.user_extension&.teacher? json.user_identity @user.identity json.tidding_count 0 json.user_phone_binded @user.phone.present? +json.phone @user.phone json.profile_completed @user.profile_completed? if @course json.course_identity @course_identity diff --git a/app/views/users/private_message_details/show.json.jbuilder b/app/views/users/private_message_details/show.json.jbuilder new file mode 100644 index 000000000..065767fb4 --- /dev/null +++ b/app/views/users/private_message_details/show.json.jbuilder @@ -0,0 +1,11 @@ +json.count @count +json.messages do + json.array! @messages.each do |message| + json.extract! message, :id, :user_id, :receiver_id, :sender_id, :content + + json.send_time message.display_send_time + json.sender do + json.partial! 'users/user_simple', user: message.sender + end + end +end \ No newline at end of file diff --git a/app/views/users/private_messages/create.json.jbuilder b/app/views/users/private_messages/create.json.jbuilder new file mode 100644 index 000000000..888cfeff6 --- /dev/null +++ b/app/views/users/private_messages/create.json.jbuilder @@ -0,0 +1,10 @@ +json.status 0 +json.message 'success' +json.private_message do + json.extract! @message, :id, :user_id, :receiver_id, :sender_id, :content + + json.send_time @message.display_send_time + json.sender do + json.partial! 'users/user_simple', user: @message.sender + end +end \ No newline at end of file diff --git a/app/views/users/private_messages/index.json.jbuilder b/app/views/users/private_messages/index.json.jbuilder new file mode 100644 index 000000000..c18a7d209 --- /dev/null +++ b/app/views/users/private_messages/index.json.jbuilder @@ -0,0 +1,13 @@ +json.count @count +json.private_messages do + json.array! @messages.each do |message| + json.extract! message, :id, :content, :message_count + + json.unread message.unread? + json.send_time message.display_send_time + + json.target do + json.partial! 'users/user_simple', user: message.target + end + end +end \ No newline at end of file diff --git a/app/views/users/recent_contacts/index.json.jbuilder b/app/views/users/recent_contacts/index.json.jbuilder new file mode 100644 index 000000000..f2f7f025a --- /dev/null +++ b/app/views/users/recent_contacts/index.json.jbuilder @@ -0,0 +1,2 @@ +json.users @contacts, partial: 'users/user_simple', as: :user +json.count @contacts.size \ No newline at end of file diff --git a/app/views/users_for_private_messages/index.json.jbuilder b/app/views/users_for_private_messages/index.json.jbuilder new file mode 100644 index 000000000..be040e368 --- /dev/null +++ b/app/views/users_for_private_messages/index.json.jbuilder @@ -0,0 +1,2 @@ +json.users @users, partial: 'users/user_simple', as: :user +json.count @users.size \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 6623edd99..2f9eed61b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -53,6 +53,11 @@ Rails.application.routes.draw do resource :grade_records, only: [:show] resource :watch, only: [:create, :destroy] resources :project_packages, only: [:index] + # 私信 + resources :private_messages, only: [:index, :create, :destroy] + resources :recent_contacts, only: [:index] + resource :private_message_details, only: [:show] + resource :unread_message_info, only: [:show] end @@ -91,6 +96,7 @@ Rails.application.routes.draw do end end end + resources :users_for_private_messages, only: [:index] resources :myshixuns, param: :identifier, shallow: true do member do diff --git a/db/migrate/20190730024112_add_index_to_user.rb b/db/migrate/20190730024112_add_index_to_user.rb new file mode 100644 index 000000000..fa198cffa --- /dev/null +++ b/db/migrate/20190730024112_add_index_to_user.rb @@ -0,0 +1,7 @@ +class AddIndexToUser < ActiveRecord::Migration[5.2] + def change + # add_index :users, :login, unique: true + # add_index :users, :mail, unique: true + # add_index :users, :phone, unique: true + end +end diff --git a/public/images/educoder/project_packages/ai.png b/public/images/educoder/project_packages/ai.png new file mode 100755 index 000000000..825e75165 Binary files /dev/null and b/public/images/educoder/project_packages/ai.png differ diff --git a/public/images/educoder/project_packages/backend.png b/public/images/educoder/project_packages/backend.png new file mode 100755 index 000000000..359bca746 Binary files /dev/null and b/public/images/educoder/project_packages/backend.png differ diff --git a/public/images/educoder/project_packages/cloud_compute_and_big_data.png b/public/images/educoder/project_packages/cloud_compute_and_big_data.png new file mode 100755 index 000000000..42e64da3b Binary files /dev/null and b/public/images/educoder/project_packages/cloud_compute_and_big_data.png differ diff --git a/public/images/educoder/project_packages/database.png b/public/images/educoder/project_packages/database.png new file mode 100755 index 000000000..2b668e774 Binary files /dev/null and b/public/images/educoder/project_packages/database.png differ diff --git a/public/images/educoder/project_packages/devops_and_test.png b/public/images/educoder/project_packages/devops_and_test.png new file mode 100755 index 000000000..77b8e6f3b Binary files /dev/null and b/public/images/educoder/project_packages/devops_and_test.png differ diff --git a/public/images/educoder/project_packages/front.png b/public/images/educoder/project_packages/front.png new file mode 100755 index 000000000..23ba2a755 Binary files /dev/null and b/public/images/educoder/project_packages/front.png differ diff --git a/public/images/educoder/project_packages/mobile.png b/public/images/educoder/project_packages/mobile.png new file mode 100755 index 000000000..cd73520c7 Binary files /dev/null and b/public/images/educoder/project_packages/mobile.png differ diff --git a/public/images/educoder/project_packages/other.png b/public/images/educoder/project_packages/other.png new file mode 100755 index 000000000..a700cd5de Binary files /dev/null and b/public/images/educoder/project_packages/other.png differ diff --git a/public/javascripts/download/jquery.fileDownload.js b/public/javascripts/download/jquery.fileDownload.js new file mode 100644 index 000000000..0d0301c23 --- /dev/null +++ b/public/javascripts/download/jquery.fileDownload.js @@ -0,0 +1,490 @@ +/* +* jQuery File Download Plugin v1.4.5 +* +* http://www.johnculviner.com +* +* Copyright (c) 2013 - John Culviner +* +* Licensed under the MIT license: +* http://www.opensource.org/licenses/mit-license.php +* +* !!!!NOTE!!!! +* You must also write a cookie in conjunction with using this plugin in the server's response headers containing the file download: +* Set-Cookie: fileDownload=true; path=/" +* !!!!NOTE!!!! +*/ + +(function($, window){ + // i'll just put them here to get evaluated on script load + var htmlSpecialCharsRegEx = /[<>&\r\n"']/gm; + var htmlSpecialCharsPlaceHolders = { + '<': 'lt;', + '>': 'gt;', + '&': 'amp;', + '\r': "#13;", + '\n': "#10;", + '"': 'quot;', + "'": '#39;' /*single quotes just to be safe, IE8 doesn't support ', so use ' instead */ + }; + +$.extend({ + // + //$.fileDownload('/path/to/url/', options) + // see directly below for possible 'options' + fileDownload: function (fileUrl, options) { + + //provide some reasonable defaults to any unspecified options below + var settings = $.extend({ + + // + //Requires jQuery UI: provide a message to display to the user when the file download is being prepared before the browser's dialog appears + // + preparingMessageHtml: null, + + // + //Requires jQuery UI: provide a message to display to the user when a file download fails + // + failMessageHtml: null, + + // + //the stock android browser straight up doesn't support file downloads initiated by a non GET: http://code.google.com/p/android/issues/detail?id=1780 + //specify a message here to display if a user tries with an android browser + //if jQuery UI is installed this will be a dialog, otherwise it will be an alert + //Set to null to disable the message and attempt to download anyway + // + androidPostUnsupportedMessageHtml: "Unfortunately your Android browser doesn't support this type of file download. Please try again with a different browser.", + + // + //Requires jQuery UI: options to pass into jQuery UI Dialog + // + dialogOptions: { modal: true }, + + // + //a function to call while the dowload is being prepared before the browser's dialog appears + //Args: + // url - the original url attempted + // + prepareCallback: function (url) { }, + + // + //a function to call after a file download successfully completed + //Args: + // url - the original url attempted + // + successCallback: function (url) { }, + + // + //a function to call after a file download request was canceled + //Args: + // url - the original url attempted + // + abortCallback: function (url) { }, + + // + //a function to call after a file download failed + //Args: + // responseHtml - the html that came back in response to the file download. this won't necessarily come back depending on the browser. + // in less than IE9 a cross domain error occurs because 500+ errors cause a cross domain issue due to IE subbing out the + // server's error message with a "helpful" IE built in message + // url - the original url attempted + // error - original error cautch from exception + // + failCallback: function (responseHtml, url, error) { }, + + // + // the HTTP method to use. Defaults to "GET". + // + httpMethod: "GET", + + // + // if specified will perform a "httpMethod" request to the specified 'fileUrl' using the specified data. + // data must be an object (which will be $.param serialized) or already a key=value param string + // + data: null, + + // + //a period in milliseconds to poll to determine if a successful file download has occured or not + // + checkInterval: 100, + + // + //the cookie name to indicate if a file download has occured + // + cookieName: "fileDownload", + + // + //the cookie value for the above name to indicate that a file download has occured + // + cookieValue: "true", + + // + //the cookie path for above name value pair + // + cookiePath: "/", + + // + //if specified it will be used when attempting to clear the above name value pair + //useful for when downloads are being served on a subdomain (e.g. downloads.example.com) + // + cookieDomain: null, + + // + //the title for the popup second window as a download is processing in the case of a mobile browser + // + popupWindowTitle: "Initiating file download...", + + // + //Functionality to encode HTML entities for a POST, need this if data is an object with properties whose values contains strings with quotation marks. + //HTML entity encoding is done by replacing all &,<,>,',",\r,\n characters. + //Note that some browsers will POST the string htmlentity-encoded whilst others will decode it before POSTing. + //It is recommended that on the server, htmlentity decoding is done irrespective. + // + encodeHTMLEntities: true + + }, options); + + var deferred = new $.Deferred(); + + //Setup mobile browser detection: Partial credit: http://detectmobilebrowser.com/ + var userAgent = (navigator.userAgent || navigator.vendor || window.opera).toLowerCase(); + + var isIos; //has full support of features in iOS 4.0+, uses a new window to accomplish this. + var isAndroid; //has full support of GET features in 4.0+ by using a new window. Non-GET is completely unsupported by the browser. See above for specifying a message. + var isOtherMobileBrowser; //there is no way to reliably guess here so all other mobile devices will GET and POST to the current window. + + if (/ip(ad|hone|od)/.test(userAgent)) { + + isIos = true; + + } else if (userAgent.indexOf('android') !== -1) { + + isAndroid = true; + + } else { + + isOtherMobileBrowser = /avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|playbook|silk|iemobile|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(userAgent.substr(0, 4)); + + } + + var httpMethodUpper = settings.httpMethod.toUpperCase(); + + if (isAndroid && httpMethodUpper !== "GET" && settings.androidPostUnsupportedMessageHtml) { + //the stock android browser straight up doesn't support file downloads initiated by non GET requests: http://code.google.com/p/android/issues/detail?id=1780 + + if ($().dialog) { + $("
").html(settings.androidPostUnsupportedMessageHtml).dialog(settings.dialogOptions); + } else { + alert(settings.androidPostUnsupportedMessageHtml); + } + + return deferred.reject(); + } + + var $preparingDialog = null; + + var internalCallbacks = { + + onPrepare: function (url) { + + //wire up a jquery dialog to display the preparing message if specified + if (settings.preparingMessageHtml) { + + $preparingDialog = $("
").html(settings.preparingMessageHtml).dialog(settings.dialogOptions); + + } else if (settings.prepareCallback) { + + settings.prepareCallback(url); + + } + + }, + + onSuccess: function (url) { + + //remove the perparing message if it was specified + if ($preparingDialog) { + $preparingDialog.dialog('close'); + } + + settings.successCallback(url); + + deferred.resolve(url); + }, + + onAbort: function (url) { + + //remove the perparing message if it was specified + if ($preparingDialog) { + $preparingDialog.dialog('close'); + }; + + settings.abortCallback(url); + + deferred.reject(url); + }, + + onFail: function (responseHtml, url, error) { + + //remove the perparing message if it was specified + if ($preparingDialog) { + $preparingDialog.dialog('close'); + } + + //wire up a jquery dialog to display the fail message if specified + if (settings.failMessageHtml) { + $("
").html(settings.failMessageHtml).dialog(settings.dialogOptions); + } + + settings.failCallback(responseHtml, url, error); + + deferred.reject(responseHtml, url); + } + }; + + internalCallbacks.onPrepare(fileUrl); + + //make settings.data a param string if it exists and isn't already + if (settings.data !== null && typeof settings.data !== "string") { + settings.data = $.param(settings.data); + } + + + var $iframe, + downloadWindow, + formDoc, + $form; + + if (httpMethodUpper === "GET") { + + if (settings.data !== null) { + //need to merge any fileUrl params with the data object + + var qsStart = fileUrl.indexOf('?'); + + if (qsStart !== -1) { + //we have a querystring in the url + + if (fileUrl.substring(fileUrl.length - 1) !== "&") { + fileUrl = fileUrl + "&"; + } + } else { + + fileUrl = fileUrl + "?"; + } + + fileUrl = fileUrl + settings.data; + } + + if (isIos || isAndroid) { + + downloadWindow = window.open(fileUrl); + downloadWindow.document.title = settings.popupWindowTitle; + window.focus(); + + } else if (isOtherMobileBrowser) { + + window.location(fileUrl); + + } else { + + //create a temporary iframe that is used to request the fileUrl as a GET request + $iframe = $("").appendTo("body"); + } + + } else { + + var formInnerHtml = ""; + + if (settings.data !== null) { + + $.each(settings.data.replace(/\+/g, ' ').split("&"), function () { + + var kvp = this.split("="); + + //Issue: When value contains sign '=' then the kvp array does have more than 2 items. We have to join value back + var k = kvp[0]; + kvp.shift(); + var v = kvp.join("="); + kvp = [k, v]; + + var key = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[0])) : decodeURIComponent(kvp[0]); + if (key) { + var value = settings.encodeHTMLEntities ? htmlSpecialCharsEntityEncode(decodeURIComponent(kvp[1])) : decodeURIComponent(kvp[1]); + formInnerHtml += ''; + } + }); + } + + if (isOtherMobileBrowser) { + + $form = $("
").appendTo("body"); + $form.hide() + .prop('method', settings.httpMethod) + .prop('action', fileUrl) + .html(formInnerHtml); + + } else { + + if (isIos) { + + downloadWindow = window.open("about:blank"); + downloadWindow.document.title = settings.popupWindowTitle; + formDoc = downloadWindow.document; + window.focus(); + + } else { + + $iframe = $("").appendTo("body"); + formDoc = getiframeDocument($iframe); + } + + formDoc.write("" + formInnerHtml + "
" + settings.popupWindowTitle + ""); + $form = $(formDoc).find('form'); + } + + $form.submit(); + } + + + //check if the file download has completed every checkInterval ms + setTimeout(checkFileDownloadComplete, settings.checkInterval); + + + function checkFileDownloadComplete() { + //has the cookie been written due to a file download occuring? + + var cookieValue = settings.cookieValue; + if(typeof cookieValue == 'string') { + cookieValue = cookieValue.toLowerCase(); + } + + var lowerCaseCookie = settings.cookieName.toLowerCase() + "=" + cookieValue; + + if (document.cookie.toLowerCase().indexOf(lowerCaseCookie) > -1) { + + //execute specified callback + internalCallbacks.onSuccess(fileUrl); + + //remove cookie + var cookieData = settings.cookieName + "=; path=" + settings.cookiePath + "; expires=" + new Date(0).toUTCString() + ";"; + if (settings.cookieDomain) cookieData += " domain=" + settings.cookieDomain + ";"; + document.cookie = cookieData; + + //remove iframe + cleanUp(false); + + return; + } + + //has an error occured? + //if neither containers exist below then the file download is occuring on the current window + if (downloadWindow || $iframe) { + + //has an error occured? + try { + + var formDoc = downloadWindow ? downloadWindow.document : getiframeDocument($iframe); + + if (formDoc && formDoc.body !== null && formDoc.body.innerHTML.length) { + + var isFailure = true; + + if ($form && $form.length) { + var $contents = $(formDoc.body).contents().first(); + + try { + if ($contents.length && $contents[0] === $form[0]) { + isFailure = false; + } + } catch (e) { + if (e && e.number == -2146828218) { + // IE 8-10 throw a permission denied after the form reloads on the "$contents[0] === $form[0]" comparison + isFailure = true; + } else { + throw e; + } + } + } + + if (isFailure) { + // IE 8-10 don't always have the full content available right away, they need a litle bit to finish + setTimeout(function () { + internalCallbacks.onFail(formDoc.body.innerHTML, fileUrl); + cleanUp(true); + }, 100); + + return; + } + } + } + catch (err) { + + //500 error less than IE9 + internalCallbacks.onFail('', fileUrl, err); + + cleanUp(true); + + return; + } + } + + + //keep checking... + setTimeout(checkFileDownloadComplete, settings.checkInterval); + } + + //gets an iframes document in a cross browser compatible manner + function getiframeDocument($iframe) { + var iframeDoc = $iframe[0].contentWindow || $iframe[0].contentDocument; + if (iframeDoc.document) { + iframeDoc = iframeDoc.document; + } + return iframeDoc; + } + + function cleanUp(isFailure) { + + setTimeout(function() { + + if (downloadWindow) { + + if (isAndroid) { + downloadWindow.close(); + } + + if (isIos) { + if (downloadWindow.focus) { + downloadWindow.focus(); //ios safari bug doesn't allow a window to be closed unless it is focused + if (isFailure) { + downloadWindow.close(); + } + } + } + } + + //iframe cleanup appears to randomly cause the download to fail + //not doing it seems better than failure... + //if ($iframe) { + // $iframe.remove(); + //} + + }, 0); + } + + + function htmlSpecialCharsEntityEncode(str) { + return str.replace(htmlSpecialCharsRegEx, function(match) { + return '&' + htmlSpecialCharsPlaceHolders[match]; + }); + } + var promise = deferred.promise(); + promise.abort = function() { + cleanUp(); + $iframe.attr('src', '').html(''); + internalCallbacks.onAbort(fileUrl); + }; + return promise; + } +}); + +})(jQuery, this || window); \ No newline at end of file diff --git a/public/javascripts/download/jquery.fileDownload.min.js b/public/javascripts/download/jquery.fileDownload.min.js new file mode 100644 index 000000000..c4ae8b923 --- /dev/null +++ b/public/javascripts/download/jquery.fileDownload.min.js @@ -0,0 +1 @@ +!function(e,o){var t=/[<>&\r\n"']/gm,a={"<":"lt;",">":"gt;","&":"amp;","\r":"#13;","\n":"#10;",'"':"quot;","'":"#39;"};e.extend({fileDownload:function(i,n){var r,l,c,d=e.extend({preparingMessageHtml:null,failMessageHtml:null,androidPostUnsupportedMessageHtml:"Unfortunately your Android browser doesn't support this type of file download. Please try again with a different browser.",dialogOptions:{modal:!0},prepareCallback:function(e){},successCallback:function(e){},abortCallback:function(e){},failCallback:function(e,o,t){},httpMethod:"GET",data:null,checkInterval:100,cookieName:"fileDownload",cookieValue:"true",cookiePath:"/",cookieDomain:null,popupWindowTitle:"Initiating file download...",encodeHTMLEntities:!0},n),s=new e.Deferred,p=(navigator.userAgent||navigator.vendor||o.opera).toLowerCase();/ip(ad|hone|od)/.test(p)?r=!0:-1!==p.indexOf("android")?l=!0:c=/avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|playbook|silk|iemobile|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(p)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(p.substr(0,4));var m=d.httpMethod.toUpperCase();if(l&&"GET"!==m&&d.androidPostUnsupportedMessageHtml)return e().dialog?e("
").html(d.androidPostUnsupportedMessageHtml).dialog(d.dialogOptions):alert(d.androidPostUnsupportedMessageHtml),s.reject();var u,f,g,h,b=null,k={onPrepare:function(o){d.preparingMessageHtml?b=e("
").html(d.preparingMessageHtml).dialog(d.dialogOptions):d.prepareCallback&&d.prepareCallback(o)},onSuccess:function(e){b&&b.dialog("close"),d.successCallback(e),s.resolve(e)},onAbort:function(e){b&&b.dialog("close"),d.abortCallback(e),s.reject(e)},onFail:function(o,t,a){b&&b.dialog("close"),d.failMessageHtml&&e("
").html(d.failMessageHtml).dialog(d.dialogOptions),d.failCallback(o,t,a),s.reject(o,t)}};if(k.onPrepare(i),null!==d.data&&"string"!=typeof d.data&&(d.data=e.param(d.data)),"GET"===m){if(null!==d.data)-1!==i.indexOf("?")?"&"!==i.substring(i.length-1)&&(i+="&"):i+="?",i+=d.data;r||l?((f=o.open(i)).document.title=d.popupWindowTitle,o.focus()):c?o.location(i):u=e("").appendTo("body")}else{var v="";null!==d.data&&e.each(d.data.replace(/\+/g," ").split("&"),function(){var e=this.split("="),o=e[0];e.shift(),e=[o,e.join("=")];var t=d.encodeHTMLEntities?T(decodeURIComponent(e[0])):decodeURIComponent(e[0]);if(t){var a=d.encodeHTMLEntities?T(decodeURIComponent(e[1])):decodeURIComponent(e[1]);v+=''}}),c?(h=e("
").appendTo("body")).hide().prop("method",d.httpMethod).prop("action",i).html(v):(r?((f=o.open("about:blank")).document.title=d.popupWindowTitle,g=f.document,o.focus()):g=w(u=e("").appendTo("body")),g.write(""+v+"
"+d.popupWindowTitle+""),h=e(g).find("form")),h.submit()}function w(e){var o=e[0].contentWindow||e[0].contentDocument;return o.document&&(o=o.document),o}function y(e){setTimeout(function(){f&&(l&&f.close(),r&&f.focus&&(f.focus(),e&&f.close()))},0)}function T(e){return e.replace(t,function(e){return"&"+a[e]})}setTimeout(function o(){var t=d.cookieValue;"string"==typeof t&&(t=t.toLowerCase());var a=d.cookieName.toLowerCase()+"="+t;if(document.cookie.toLowerCase().indexOf(a)>-1){k.onSuccess(i);var n=d.cookieName+"=; path="+d.cookiePath+"; expires="+new Date(0).toUTCString()+";";return d.cookieDomain&&(n+=" domain="+d.cookieDomain+";"),document.cookie=n,void y(!1)}if(f||u)try{var r=f?f.document:w(u);if(r&&null!==r.body&&r.body.innerHTML.length){var l=!0;if(h&&h.length){var c=e(r.body).contents().first();try{c.length&&c[0]===h[0]&&(l=!1)}catch(e){if(!e||-2146828218!=e.number)throw e;l=!0}}if(l)return void setTimeout(function(){k.onFail(r.body.innerHTML,i),y(!0)},100)}}catch(e){return k.onFail("",i,e),void y(!0)}setTimeout(o,d.checkInterval)},d.checkInterval);var x=s.promise();return x.abort=function(){y(),u.attr("src","").html(""),k.onAbort(i)},x}})}(jQuery,this||window); \ No newline at end of file diff --git a/public/react/src/AppConfig.js b/public/react/src/AppConfig.js index 2ad7df0d5..363ff495d 100644 --- a/public/react/src/AppConfig.js +++ b/public/react/src/AppConfig.js @@ -41,7 +41,7 @@ export function initAxiosInterceptors(props) { // proxy = "http://testbdweb.trustie.net" // proxy = "http://testbdweb.educoder.net" // proxy = "https://testeduplus2.educoder.net" - proxy="http://47.96.87.25:48080/" + proxy="http://47.96.87.25:48080" // 在这里使用requestMap控制,避免用户通过双击等操作发出重复的请求; diff --git a/public/react/src/common/TextUtil.js b/public/react/src/common/TextUtil.js index 8aef0adbd..bb0b3f145 100644 --- a/public/react/src/common/TextUtil.js +++ b/public/react/src/common/TextUtil.js @@ -1,4 +1,6 @@ -import { bytesToSize } from 'educoder'; +import { bytesToSize, getUrl2 } from 'educoder'; +const $ = window.$ + export function isImageExtension(fileName) { return fileName ? !!(fileName.match(/.(jpg|jpeg|png|gif)$/i)) : false } @@ -36,6 +38,25 @@ export function markdownToHTML(oldContent, selector) { return content } } +function _doDownload(options) { + $.fileDownload("/api" + options.url, { + successCallback: options.successCallback, + failCallback: options.failCallback + }); +} +export function downloadFile(options) { + if ($.fileDownload) { + _doDownload(options) + } else { + const _url_origin = getUrl2() + $.getScript( + `${_url_origin}/javascripts/download/jquery.fileDownload.min.js`, + (data, textStatus, jqxhr) => { + _doDownload(options) + }); + } + +} export function appendFileSizeToUploadFile(item) { return `${item.title}${uploadNameSizeSeperator}${item.filesize}` diff --git a/public/react/src/common/educoder.js b/public/react/src/common/educoder.js index f090d2c39..44b3057cc 100644 --- a/public/react/src/common/educoder.js +++ b/public/react/src/common/educoder.js @@ -15,7 +15,8 @@ export { updatePageParams as updatePageParams } from './RouterUti export { bytesToSize as bytesToSize } from './UnitUtil'; -export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension } from './TextUtil' +export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension, + downloadFile } from './TextUtil' export { handleDateString, getNextHalfHourOfMoment,formatDuring } from './DateUtil' diff --git a/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js b/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js index f1b9faab7..a690fedc0 100644 --- a/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js +++ b/public/react/src/modules/courses/busyWork/CommonWorkDetailIndex.js @@ -8,7 +8,7 @@ import { CNotificationHOC } from '../common/CNotificationHOC' import { RouteHOC } from './common' import locale from 'antd/lib/date-picker/locale/zh_CN'; -import { WordsBtn, MarkdownToHtml, trigger, queryString } from 'educoder'; +import { WordsBtn, MarkdownToHtml, trigger, queryString, downloadFile } from 'educoder'; import axios from 'axios'; import Modals from '../../modals/Modals'; import CoursesListType from '../coursesPublic/CoursesListType'; @@ -18,6 +18,8 @@ import '../css/Courses.css' import CBreadcrumb from '../common/CBreadcrumb' import DownloadMessageysl from "../../modals/DownloadMessageysl"; +import { Spin } from 'antd' + //引入对应跳转的组件 //新建分组/普通作业 @@ -64,6 +66,7 @@ class CommonWorkDetailIndex extends Component{ this.state = { DownloadType:false, DownloadMessageval:undefined, + donwloading: false, } } initWorkDetailCommonState = (data) => { @@ -123,7 +126,20 @@ class CommonWorkDetailIndex extends Component{ } }else { this.props.showNotification(`正在下载中`); - window.open("/api"+url, '_blank'); + + this.setState({ donwloading: true }) + downloadFile({ + url: url, + successCallback: (url) => { + this.setState({ donwloading: false }) + console.log('successCallback') + }, + failCallback: (responseHtml, url) => { + this.setState({ donwloading: false }) + console.log('failCallback') + } + }) + // window.open("/api"+url, '_blank'); } }).catch((error) => { console.log(error) @@ -246,7 +262,7 @@ class CommonWorkDetailIndex extends Component{ }
- { noTab !== true &&
+ { noTab !== true &&
this.setState({moduleName: '作品列表'})} @@ -285,18 +301,33 @@ class CommonWorkDetailIndex extends Component{ padding-top: 10px; padding-bottom: 8px; } + + .floatSpinParent .ant-spin-nested-loading { + float: right; + } + .floatSpinParent .ant-spin-nested-loading .ant-spin-spinning{ + top: 10px; + } `} - {this.props.isAdmin()?
  • + {this.props.isAdmin()? +
  • 导出 -
  • :""} + + :""} {/* {isAdmin && 导出作品附件} {isAdmin && 导出成绩} */} diff --git a/public/react/src/modules/courses/exercise/Testpapersettinghomepage.js b/public/react/src/modules/courses/exercise/Testpapersettinghomepage.js index ddb448cfc..36b319f5a 100644 --- a/public/react/src/modules/courses/exercise/Testpapersettinghomepage.js +++ b/public/react/src/modules/courses/exercise/Testpapersettinghomepage.js @@ -267,7 +267,7 @@ class Testpapersettinghomepage extends Component{

    {this.props.coursedata.name} > - 试卷 + 试卷 > 试卷详情

    diff --git a/public/react/src/modules/projectPackages/PackageIndex/PackageConcent.js b/public/react/src/modules/projectPackages/PackageIndex/PackageConcent.js index 46e36f90f..cfc54bb98 100644 --- a/public/react/src/modules/projectPackages/PackageIndex/PackageConcent.js +++ b/public/react/src/modules/projectPackages/PackageIndex/PackageConcent.js @@ -6,29 +6,30 @@ import moment from 'moment'; import '../packageconcnet.css'; const { Search } = Input; -// let categorylist=[ -// {name:"全部",value:undefined}, -// {name:"前端开发",value:"front"}, -// {name:"后端开发",value:"backend"}, -// {name:"移动开发",value:"mobile"}, -// {name:"数据库",value:"database"}, -// {name:"云计算和大数据",value:"cloud_compute_and_big_data"}, -// {name:"人工智能",value:"ai"}, -// {name:"其他",value:"other"}, -// ] +let categorylist=[ + {name:"全部",value:undefined}, + {name:"前端开发",value:"front"}, + {name:"后端开发",value:"backend"}, + {name:"移动开发",value:"mobile"}, + {name:"数据库",value:"database"}, + {name:"云计算和大数据",value:"cloud_compute_and_big_data"}, + {name:"人工智能",value:"ai"}, + {name:"运维与测试",value:"devops_and_test"}, + {name:"其他",value:"other"}, +] // -// function setcategorylist(val){ -// let vals="" -// categorylist.some((item,key)=> { -// if (item.value === val) { -// vals=item.name -// return true -// } -// } -// ) -// -// return vals -// } +function setcategorylist(val){ + let vals="" + categorylist.some((item,key)=> { + if (item.name === val) { + vals=item.value + return true + } + } + ) + + return vals +} @@ -157,7 +158,7 @@ class PackageConcent extends Component { enterButton={ 搜索} onSearch={ (value)=>this.setdatafuns(value)} />

    @@ -214,7 +215,7 @@ class PackageConcent extends Component {
    - +
    @@ -223,7 +224,7 @@ class PackageConcent extends Component { diff --git a/public/react/src/modules/projectPackages/PackageIndexNEITaskDetails/PackageIndexNEITaskDetails.js b/public/react/src/modules/projectPackages/PackageIndexNEITaskDetails/PackageIndexNEITaskDetails.js index 23d7a095c..4788a7dc1 100644 --- a/public/react/src/modules/projectPackages/PackageIndexNEITaskDetails/PackageIndexNEITaskDetails.js +++ b/public/react/src/modules/projectPackages/PackageIndexNEITaskDetails/PackageIndexNEITaskDetails.js @@ -229,7 +229,8 @@ class PackageIndexNEITaskDetails extends Component { } goback = () => { - window.history.go(-1) + // window.history.go(-1) + window.location.href="/crowdsourcings"; } render() { @@ -251,7 +252,7 @@ class PackageIndexNEITaskDetails extends Component { '} className={"fl"}> {/*{this.props.current_user.username}*/} - 众包创新 + 众包创新 {data&&data.title} @@ -280,7 +281,7 @@ class PackageIndexNEITaskDetails extends Component {
    + src={data&&data.creator.image_url} width="70"/>
    {data&&data.creator.name} @@ -290,8 +291,8 @@ class PackageIndexNEITaskDetails extends Component { onMouseOver={this.setover} onMouseOut={this.setout} > - {overtype===false? 头像联系TA: - 头像 头像联系TA: + 头像联系TA}
    }
    @@ -351,7 +352,7 @@ class PackageIndexNEITaskDetails extends Component { 需求详情: {data&&data.status==="pending"&&data&&data.operation.can_select_bidding_user===true?:""} @@ -393,18 +394,18 @@ class PackageIndexNEITaskDetails extends Component {
    -
    -
    -
    +
    +
    +
    {data&&data.bidding_users.map((item,key)=>{ return( -
    +
    {item.status==="bidding_won"?:""} - +

    {item.name}

    - {this.props.current_user&&this.props.current_user.login!=item.login? + {this.props.current_user&&this.props.current_user.login!=item.login? 头像联系TA :""}
    @@ -442,15 +443,15 @@ class PackageIndexNEITaskDetails extends Component {
    -
    -
    -
    +
    +
    +
    {data&&data.bidding_users.map((item,key)=>{ return( -
    this.Clickteacher2(item.id)} key={key}> +
    this.Clickteacher2(item.id)} key={key}> {item.bool===true?:} - +

    {item.name}

    ) diff --git a/public/react/src/modules/projectPackages/PackageIndexNEITaskDetails/pds.css b/public/react/src/modules/projectPackages/PackageIndexNEITaskDetails/pds.css index a6f9e10dc..504a78bed 100644 --- a/public/react/src/modules/projectPackages/PackageIndexNEITaskDetails/pds.css +++ b/public/react/src/modules/projectPackages/PackageIndexNEITaskDetails/pds.css @@ -1,4 +1,4 @@ -.ysldivhome2{ +.ysldivhome2s{ display: flex; flex-flow: row wrap; align-content:stretch; @@ -6,20 +6,17 @@ margin-bottom: 20px; } -.ysllogin_register_contents{ - display: flex; - margin-top: 20px; - /*justify-content: center;*/ - background: #fff; -} - .ysllogin_register_contentss{ margin-top:0px !important; padding-top: 10px; padding-bottom: 10px; + display: flex; + margin-top: 20px; + /*justify-content: center;*/ + background: #fff; } -.ysldivhomediv1{ +.ysldivhomediv1s{ width: 80px; height: 130px; display: flex; @@ -33,10 +30,10 @@ margin-left: 64px; } -.yslgouxuanimg2{ +.yslgouxuanimg2s{ height: 20px; } -.div1img{ +.div1imgs{ display: flex; justify-content:center; width: 80px; @@ -53,7 +50,7 @@ .ptext{ width: 80px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } -.ysllogin_section { +.ysllogin_sections { display: flex; align-items: center; flex-direction: column; diff --git a/public/react/src/modules/projectPackages/PackageIndexNewandEdit/PackageIndexNEIBannerConcent.js b/public/react/src/modules/projectPackages/PackageIndexNewandEdit/PackageIndexNEIBannerConcent.js index e7e4b20a2..c9d49e542 100644 --- a/public/react/src/modules/projectPackages/PackageIndexNewandEdit/PackageIndexNEIBannerConcent.js +++ b/public/react/src/modules/projectPackages/PackageIndexNewandEdit/PackageIndexNEIBannerConcent.js @@ -407,7 +407,7 @@ class PackageIndexNEIBannerConcent extends Component { // } - if(modalCancel===true||this.props.current_user.phone===null){ + if(this.props.current_user&&this.props.current_user.phone===null||modalCancel===true){ if(contact_phone===undefined||contact_phone===null||contact_phone===""){ this.setState({ contact_phonetype:true @@ -445,7 +445,7 @@ class PackageIndexNEIBannerConcent extends Component { min_price:parseInt(min_price), max_price:parseInt(max_price), contact_name: contact_name===null||contact_name===undefined?this.props.current_user.username:contact_name, - contact_phone: contact_phone===undefined?this.props.current_user.phone:contact_phone, + contact_phone: contact_phone===undefined?this.props.current_user&&this.props.current_user.phone:contact_phone, code:code, publish:types } @@ -454,7 +454,7 @@ class PackageIndexNEIBannerConcent extends Component { if(type===true){ this.props.setPublicationfun(response.data.id) }else{ - window.location.href="/crowdsourcing/"+response.data.id + window.location.href="/crowdsourcings/"+response.data.id } this.setState({ springtype:false @@ -493,7 +493,7 @@ class PackageIndexNEIBannerConcent extends Component { min_price:parseInt(min_price), max_price:parseInt(max_price), contact_name: contact_name===null||contact_name===undefined?this.props.current_user.username:contact_name, - contact_phone: contact_phone===undefined?this.props.current_user.phone:contact_phone, + contact_phone: contact_phone===undefined?this.props.current_user&&this.props.current_user.phone:contact_phone, code:code, publish:types } @@ -502,7 +502,7 @@ class PackageIndexNEIBannerConcent extends Component { if(type===true){ this.props.setPublicationfun(response.data.id) }else{ - window.location.href="/crowdsourcing/"+response.data.id + window.location.href="/crowdsourcings/"+response.data.id } this.setState({ springtype:false @@ -629,7 +629,7 @@ class PackageIndexNEIBannerConcent extends Component { // }) // } // } - if(modalCancel===true||this.props.current_user.phone===null){ + if(this.props.current_user&&this.props.current_user.phone===null||modalCancel===true){ if(e.target.value===undefined||e.target.value===null||e.target.value===""){ this.setState({ contact_phonetype:true @@ -709,7 +709,7 @@ class PackageIndexNEIBannerConcent extends Component {
    - +

    @@ -724,7 +724,7 @@ class PackageIndexNEIBannerConcent extends Component { {this.state.categorytypes===true?

    请选择类型
    :""}
    - +
    - +

    竞标截止: @@ -833,26 +833,26 @@ class PackageIndexNEIBannerConcent extends Component { {this.state.minmaxtype===true?

    最高费用不能小于最低费用
    :""}

    - +

    - 姓名: + 姓名: this.onChangeContact_name(e)} /> {this.state.contact_nametype===true?

    不能为空
    :""}

    - {modalCancel===false&&this.props.current_user.phone!=null?

    + {this.props.current_user&&this.props.current_user.phone!=null&&modalCancel===false?

    手机号: @@ -861,7 +861,7 @@ class PackageIndexNEIBannerConcent extends Component {

    :""} {/*{this.state.current_userphonetype===true?
    不能为空
    :""}*/} - {modalCancel===true||this.props.current_user.phone===null?

    + {this.props.current_user&&this.props.current_user.phone===null||modalCancel===true?

    {/*未注册才显示!*/} diff --git a/public/react/src/modules/projectPackages/PackageIndexNewandEdit/PackageIndexNEISubmit.js b/public/react/src/modules/projectPackages/PackageIndexNewandEdit/PackageIndexNEISubmit.js index 9f287e90e..a64e222e4 100644 --- a/public/react/src/modules/projectPackages/PackageIndexNewandEdit/PackageIndexNEISubmit.js +++ b/public/react/src/modules/projectPackages/PackageIndexNewandEdit/PackageIndexNEISubmit.js @@ -14,10 +14,10 @@ class PackageIndexNEISubmit extends Component { } setageload=(sum)=>{ if(sum===undefined){ - window.location.href="/crowdsourcing/new" + window.location.href="/crowdsourcings/new" }else{ // this.props.history.push("/project_packages/"+sum) - window.location.href="/crowdsourcing/"+sum + window.location.href="/crowdsourcings/"+sum } } diff --git a/public/react/src/modules/projectPackages/packageconcnet.css b/public/react/src/modules/projectPackages/packageconcnet.css index 498da9214..74119e2b4 100644 --- a/public/react/src/modules/projectPackages/packageconcnet.css +++ b/public/react/src/modules/projectPackages/packageconcnet.css @@ -344,3 +344,17 @@ color: #4CACFF!important } +.ant-form-item-requireds::before { + display: inline-block; + margin-right: 4px; + color: #f5222d; + font-size: 14px; + font-family: SimSun,sans-serif; + line-height: 1; + content: '*'; +} + +*, *::before, *::after { + -webkit-box-sizing: border-box; + box-sizing: border-box; +} \ No newline at end of file diff --git a/public/react/src/modules/tpm/NewHeader.js b/public/react/src/modules/tpm/NewHeader.js index 379c70e27..1798bfca8 100644 --- a/public/react/src/modules/tpm/NewHeader.js +++ b/public/react/src/modules/tpm/NewHeader.js @@ -816,6 +816,7 @@ submittojoinclass=(value)=>{

  • 我的实训
  • 我的实践课程
  • 我的项目
  • +
  • 我的众包
  • 账号管理
  • {/*
  • this.educoderlogin()} >登入测试接口
  • */} {/*
  • this.trialapplications()} >试用申请
  • */}