diff --git a/Gemfile b/Gemfile index 7cda93671..28df2328c 100644 --- a/Gemfile +++ b/Gemfile @@ -95,3 +95,4 @@ gem 'bulk_insert' gem 'searchkick' gem 'aasm' +gem 'enumerize' diff --git a/Gemfile.lock b/Gemfile.lock index a33cd2ef6..3e5945c70 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,6 +106,8 @@ GEM elasticsearch-transport (7.2.0) faraday multi_json + enumerize (2.3.1) + activesupport (>= 3.2) erubi (1.7.1) execjs (2.7.0) faraday (0.15.4) @@ -366,6 +368,7 @@ DEPENDENCIES byebug capybara (>= 2.15, < 4.0) chromedriver-helper + enumerize faraday (~> 0.15.4) font-awesome-sass (= 4.7.0) gitlab! diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index e7a4d28ff..2dcd7cd17 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -13,10 +13,14 @@ //= require bootstrap-datepicker //= require bootstrap.viewer //= require jquery.mloading +//= require common //= require echarts -//= require lib/codemirror -//= require mode/shell/shell +//= require codemirror/lib/codemirror +//= require codemirror/mode/shell/shell +//= require editormd/editormd +//= require editormd/languages/zh-tw +//= require dragula/dragula //= require_tree ./i18n //= require_tree ./admins @@ -41,7 +45,7 @@ $.notifyDefaults({ }); $(document).on('turbolinks:load', function(){ - $('[data-toggle="tooltip"]').tooltip(); + $('[data-toggle="tooltip"]').tooltip({ trigger : 'hover' }); $('[data-toggle="popover"]').popover(); // 图片查看大图 diff --git a/app/assets/javascripts/admins/about.js b/app/assets/javascripts/admins/about.js new file mode 100644 index 000000000..d706e653c --- /dev/null +++ b/app/assets/javascripts/admins/about.js @@ -0,0 +1,5 @@ +$(document).on('turbolinks:load', function() { + if ($('body.admins-abouts-edit-page, body.admins-abouts-update-page').length > 0) { + createMDEditor('about-us-editor', {}); + } +}) \ No newline at end of file diff --git a/app/assets/javascripts/admins/agreement.js b/app/assets/javascripts/admins/agreement.js new file mode 100644 index 000000000..387d4b337 --- /dev/null +++ b/app/assets/javascripts/admins/agreement.js @@ -0,0 +1,5 @@ +$(document).on('turbolinks:load', function() { + if ($('body.admins-agreements-edit-page, body.admins-agreements-update-page').length > 0) { + createMDEditor('agreement-editor', {}); + } +}) \ No newline at end of file diff --git a/app/assets/javascripts/admins/cooperatives.js b/app/assets/javascripts/admins/cooperatives.js new file mode 100644 index 000000000..f650e00e3 --- /dev/null +++ b/app/assets/javascripts/admins/cooperatives.js @@ -0,0 +1,96 @@ +$(document).on('turbolinks:load', function() { + if ($('body.admins-cooperatives-index-page').length > 0) { + // ------------ 保存链接 ----------- + $('.coo-img-card').on('click', '.save-url-btn', function(){ + var $link = $(this); + var cooId = $link.data('id'); + var url = $('.coo-img-item-' + cooId).find('.url-input').val(); + $link.attr('disabled', true); + + $.ajax({ + url: '/admins/cooperatives/' + cooId, + method: 'PATCH', + dataType: 'json', + data: { url: url }, + success: function(data){ + $.notify({ message: '保存成功' }); + }, + error: ajaxErrorNotifyHandler, + complete: function(){ + $link.removeAttr('disabled'); + } + }) + }); + + // ------------ 拖拽 ------------- + var onDropFunc = function(el, _target, _source, sibling){ + var moveId = $(el).data('id'); + var insertId = $(sibling).data('id') || ''; + + $.ajax({ + url: '/admins/cooperatives/drag', + method: 'POST', + dataType: 'json', + data: { move_id: moveId, after_id: insertId }, + success: function(data){ + }, + error: function(res){ + var data = res.responseJSON; + $.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'}); + } + }) + }; + var ele1 = document.getElementById('coo-img-container-alliance_coop'); + dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc); + var ele2 = document.getElementById('coo-img-container-com_coop'); + dragula([ele2], { mirrorContainer: ele2 }).on('drop', onDropFunc); + var ele3 = document.getElementById('coo-img-container-edu_coop'); + dragula([ele3], { mirrorContainer: ele3 }).on('drop', onDropFunc); + + + // ----------- 新增 -------------- + var $createModal = $('.modal.admin-add-cooperative-modal'); + var $createForm = $createModal.find('form.admin-add-cooperative-form'); + + $createForm.validate({ + errorElement: 'span', + errorClass: 'danger text-danger', + rules: { + "coo_img[image]": { + required: true + } + } + }); + + $createModal.on('show.bs.modal', function(event){ + resetFileInputFunc($createModal.find('.img-file-input')); + $createModal.find('.file-names').html('选择文件'); + + var $link = $(event.relatedTarget); + var imgType = $link.data('imgType'); + $createForm.find('input[name="coo_img[img_type]"]').val(imgType); + }); + + $createModal.on('click', '.submit-btn', function() { + $createForm.find('.error').html(''); + + if ($createForm.valid()) { + $createForm.submit(); + } else { + $createForm.find('.error').html('请选择图片'); + } + }); + $createModal.on('change', '.img-file-input', function(){ + var file = $(this)[0].files[0]; + $createModal.find('.file-names').html(file ? file.name : '请选择文件'); + }) + + // -------------- 重新上传图片 -------------- + //replace_image_url + $('.modal.admin-upload-file-modal').on('upload:success', function(e, data){ + var $cooImgItem = $('.coo-img-item-' + data.source_id); + $.post('/admins/cooperatives/'+ data.source_id + '/replace_image_url'); + $cooImgItem.find('.coo-img-item-img img').attr('src', data.url); + }) + } +}) \ No newline at end of file diff --git a/app/assets/javascripts/admins/help-center.js b/app/assets/javascripts/admins/help-center.js new file mode 100644 index 000000000..5fd6055df --- /dev/null +++ b/app/assets/javascripts/admins/help-center.js @@ -0,0 +1,5 @@ +$(document).on('turbolinks:load', function() { + if ($('body.admins-help-centers-edit-page, body.admins-help-centers-update-page').length > 0) { + createMDEditor('help-center-editor', {}); + } +}) \ No newline at end of file diff --git a/app/assets/javascripts/admins/users/index.js b/app/assets/javascripts/admins/users/index.js index 1ac936df5..c7b8e5e6a 100644 --- a/app/assets/javascripts/admins/users/index.js +++ b/app/assets/javascripts/admins/users/index.js @@ -122,10 +122,6 @@ $(document).on('turbolinks:load', function(){ // 导入学生 var $importUserModal = $('.modal.admin-import-user-modal'); var $importUserForm = $importUserModal.find('form.admin-import-user-form') - var resetFileInputFunc = function(file){ - file.after(file.clone().val("")); - file.remove(); - } $importUserModal.on('show.bs.modal', function(){ resetFileInputFunc($importUserModal.find('.upload-file-input')); diff --git a/app/assets/javascripts/common.js b/app/assets/javascripts/common.js new file mode 100644 index 000000000..fa7bef244 --- /dev/null +++ b/app/assets/javascripts/common.js @@ -0,0 +1,44 @@ +function createMDEditor(element, opts){ + var defaults = { + path: '/editormd/lib/', + syncScrolling: "single", + tex: true, + tocm: true, + emoji: true, + taskList: true, + codeFold: true, + searchReplace: true, + htmlDecode: "style,script,iframe", + sequenceDiagram: true, + autoFocus: false, + toolbarIcons: function () { + // Or return editormd.toolbarModes[name]; // full, simple, mini + // Using "||" set icons align right. + return ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "|", "image", "table", '|', "watch", "clear"] + }, + //这个配置在simple.html中并没有,但是为了能够提交表单,使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中,方便post提交表单。 + saveHTMLToTextarea: true, + dialogMaskOpacity: 0.6, + imageUpload: true, + imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"], + imageUploadURL: '/api/attachments.json' + } + var options = $.extend({}, defaults, opts); + + return editormd(element, options); +} + +function ajaxErrorNotifyHandler(res) { + var message = ''; + if(res.status !== 500){ + message = res.responseJSON.message; + } else { + message = '系统错误'; + } + return $.notify({message: message}, {type: 'danger'}); +} + +function resetFileInputFunc(file){ + file.after(file.clone().val("")); + file.remove(); +} \ No newline at end of file diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 30fd635a6..efa12b32e 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -6,7 +6,10 @@ @import "bootstrap-datepicker"; @import "bootstrap-datepicker.standalone"; @import "jquery.mloading"; -@import "lib/codemirror"; + +@import "codemirror/lib/codemirror"; +@import "editormd/css/editormd.min"; +@import "dragula/dragula"; @import "common"; @import "admins/*"; diff --git a/app/assets/stylesheets/admins/cooperatives.scss b/app/assets/stylesheets/admins/cooperatives.scss new file mode 100644 index 000000000..530ed2316 --- /dev/null +++ b/app/assets/stylesheets/admins/cooperatives.scss @@ -0,0 +1,35 @@ +.admins-cooperatives-index-page { + .coo-img-card { + .coo-img-item { + & > .drag { + cursor: move; + background: #fff; + box-shadow: 1px 2px 5px 3px #f0f0f0; + } + + &-img { + cursor: pointer; + width: 100%; + height: 40px; + margin-bottom: 10px; + + & > img { + width: 100%; + height: 40px; + } + } + + .delete-btn { + position: absolute; + top: 3px; + right: 20px; + color: red; + cursor: pointer; + } + + .save-url-btn { + cursor: pointer; + } + } + } +} \ No newline at end of file diff --git a/app/controllers/admins/abouts_controller.rb b/app/controllers/admins/abouts_controller.rb new file mode 100644 index 000000000..1f5838cbb --- /dev/null +++ b/app/controllers/admins/abouts_controller.rb @@ -0,0 +1,18 @@ +class Admins::AboutsController < Admins::BaseController + def edit + current_doc + end + + def update + current_doc.update!(about_us: params[:about_us]) + + flash[:success] = '保存成功' + redirect_to edit_admins_about_path + end + + private + + def current_doc + @doc ||= Help.first || Help.create + end +end \ No newline at end of file diff --git a/app/controllers/admins/agreements_controller.rb b/app/controllers/admins/agreements_controller.rb new file mode 100644 index 000000000..2a0d56b8a --- /dev/null +++ b/app/controllers/admins/agreements_controller.rb @@ -0,0 +1,18 @@ +class Admins::AgreementsController < Admins::BaseController + def edit + current_doc + end + + def update + current_doc.update!(agreement: params[:agreement]) + + flash[:success] = '保存成功' + redirect_to edit_admins_agreement_path + end + + private + + def current_doc + @doc ||= Help.first || Help.create + end +end \ No newline at end of file diff --git a/app/controllers/admins/contact_us_controller.rb b/app/controllers/admins/contact_us_controller.rb new file mode 100644 index 000000000..5c6a7b6e2 --- /dev/null +++ b/app/controllers/admins/contact_us_controller.rb @@ -0,0 +1,28 @@ +class Admins::ContactUsController < Admins::BaseController + def edit + @cooperations = Cooperation.all.group(:user_type) + @help = Help.first + end + + def update + cooperation = Cooperation.find(params[:id]) + cooperation.update!(update_cooperation_params) + + flash[:success] = '保存成功' + redirect_to edit_admins_contact_us_path + end + + def update_address + help = Help.first || Help.create + help.update!(status: params.dig('help', 'status')) + + flash[:success] = '保存成功' + redirect_to edit_admins_contact_us_path + end + + private + + def update_cooperation_params + params.require(:cooperation).permit(:name, :qq, :mail) + end +end \ No newline at end of file diff --git a/app/controllers/admins/cooperatives_controller.rb b/app/controllers/admins/cooperatives_controller.rb new file mode 100644 index 000000000..4c114003e --- /dev/null +++ b/app/controllers/admins/cooperatives_controller.rb @@ -0,0 +1,84 @@ +class Admins::CooperativesController < Admins::BaseController + before_action :convert_file!, only: [:create] + + def index + @data = { 'alliance_coop' => [], 'com_coop' => [], 'edu_coop' => [] } + @data = @data.merge CooImg.all.group_by(&:img_type) + end + + def create + position = CooImg.where(img_type: create_params[:img_type]).count + 1 + + ActiveRecord::Base.transaction do + coo = CooImg.create!(create_params.merge(position: position)) + + file_path = Util::FileManage.disk_filename('CooImg', coo.id) + File.delete(file_path) if File.exist?(file_path) # 删除之前的文件 + Util.write_file(@file, file_path) + + coo.update!(url_states: Util::FileManage.disk_file_url('CooImg', coo.id)) + end + + flash[:success] = '保存成功' + redirect_to admins_cooperatives_path + end + + def update + current_coo.update!(src_states: params[:url]) + render_ok + end + + def destroy + ActiveRecord::Base.transaction do + current_coo.destroy! + # 前移 + CooImg.where(img_type: current_coo.img_type).where('position > ?', current_coo.position) + .update_all('position = position - 1') + + file_path = Util::FileManage.disk_filename('CooImg', current_coo.id) + File.delete(file_path) if File.exist?(file_path) + end + render_delete_success + end + + def drag + move = CooImg.find_by(id: params[:move_id]) + after = CooImg.find_by(id: params[:after_id]) + + Admins::DragCooperativeService.call(move, after) + render_ok + rescue Admins::DragCooperativeService::Error => e + render_error(e.message) + end + + def replace_image_url + current_coo.update!(url_states: Util::FileManage.disk_file_url('CooImg', current_coo.id)) + render_ok + end + + private + + def current_coo + @_current_coo ||= CooImg.find(params[:id]) + end + + def create_params + params.require(:coo_img).permit(:img_type, :src_states) + end + + def convert_file! + max_size = 10 * 1024 * 1024 # 10M + file = params.dig('coo_img', 'image') + if file.class == ActionDispatch::Http::UploadedFile + @file = file + render_error('请上传文件') if @file.size.zero? + render_error('文件大小超过限制') if @file.size > max_size + else + file = file.to_s.strip + return render_error('请上传正确的图片') if file.blank? + @file = Util.convert_base64_image(file, max_size: max_size) + end + rescue Base64ImageConverter::Error => ex + render_error(ex.message) + end +end \ No newline at end of file diff --git a/app/controllers/admins/files_controller.rb b/app/controllers/admins/files_controller.rb index 3c799ceba..b269f8e27 100644 --- a/app/controllers/admins/files_controller.rb +++ b/app/controllers/admins/files_controller.rb @@ -6,7 +6,7 @@ class Admins::FilesController < Admins::BaseController Util.write_file(@file, file_path) - render_ok(source_id: params[:source_id], source_type: params[:source_type].to_s, url: file_url) + render_ok(source_id: params[:source_id], source_type: params[:source_type].to_s, url: file_url + "?t=#{Random.rand}") rescue StandardError => ex logger_error(ex) render_error('上传失败') @@ -33,22 +33,14 @@ class Admins::FilesController < Admins::BaseController @_file_path ||= begin case params[:source_type].to_s when 'Shixun' then - disk_filename('Shixun', params[:source_id]) + Util::FileManage.disk_filename('Shixun', params[:source_id]) else - disk_filename(params[:source_type].to_s, params[:source_id].to_s) + Util::FileManage.disk_filename(params[:source_type].to_s, params[:source_id].to_s) end end end - def disk_filename(type, id) - File.join(storage_path, type.to_s, id.to_s) - end - - def storage_path - @_storage_path ||= File.join(Rails.root, 'public', 'images', 'avatars') - end - def file_url - File.join('/images/avatars/', params[:source_type].to_s, params[:source_id].to_s) + Util::FileManage.disk_file_url(params[:source_type].to_s, params[:source_id].to_s) end end \ No newline at end of file diff --git a/app/controllers/admins/help_centers_controller.rb b/app/controllers/admins/help_centers_controller.rb new file mode 100644 index 000000000..5e8fa5a94 --- /dev/null +++ b/app/controllers/admins/help_centers_controller.rb @@ -0,0 +1,18 @@ +class Admins::HelpCentersController < Admins::BaseController + def edit + current_doc + end + + def update + current_doc.update!(help_center: params[:help_center]) + + flash[:success] = '保存成功' + redirect_to edit_admins_help_center_path + end + + private + + def current_doc + @doc ||= Help.first || Help.create + end +end \ No newline at end of file diff --git a/app/controllers/question_banks_controller.rb b/app/controllers/question_banks_controller.rb index f09a53dbe..e7be8b395 100644 --- a/app/controllers/question_banks_controller.rb +++ b/app/controllers/question_banks_controller.rb @@ -88,7 +88,7 @@ class QuestionBanksController < ApplicationController end def send_to_course - banks = object_banks + banks = @object_type.classify.constantize.where(id: params[:object_id]) course = current_user.manage_courses.find_by!(id: params[:course_id]) banks.each do |bank| case @object_type diff --git a/app/controllers/student_works_controller.rb b/app/controllers/student_works_controller.rb index 99a02106c..7eef670fc 100644 --- a/app/controllers/student_works_controller.rb +++ b/app/controllers/student_works_controller.rb @@ -8,12 +8,15 @@ class StudentWorksController < ApplicationController before_action :find_work, only: [:shixun_work_report, :adjust_review_score, :shixun_work, :commit_des, :update_des, :adjust_score, :show, :adjust_score, :supply_attachments, :revise_attachment, :comment_list, :add_score, :add_score_reply, :destroy_score, :appeal_anonymous_score, - :deal_appeal_score, :cancel_appeal, :edit, :update, :export_shixun_work_report] + :deal_appeal_score, :cancel_appeal, :edit, :update, :export_shixun_work_report, + :shixun_work_comment, :destroy_work_comment] before_action :user_course_identity before_action :allow_add_score, only: [:add_score] before_action :homework_publish - before_action :teacher_allowed, only: [:adjust_score, :adjust_review_score, :deal_appeal_score] + before_action :teacher_allowed, only: [:adjust_score, :adjust_review_score, :deal_appeal_score, :shixun_work_comment, + :destroy_work_comment] + before_action :course_student, only: [:new, :commit_des, :update_des, :create, :edit, :update, :search_member_list, :relate_project, :cancel_relate_project, :relate_project, :delete_work] @@ -455,6 +458,7 @@ class StudentWorksController < ApplicationController @shixun = @homework.shixuns.take # 提示: 这里如果includes outputs表的话: sum(:evaluate_count)会出现错误 @games = @work.myshixun.games.joins(:challenge).reorder("challenges.position asc") if @work.myshixun + @comment = @work.student_works_scores.shixun_comment.first # 用户最大评测次数 if @games @@ -468,6 +472,23 @@ class StudentWorksController < ApplicationController @echart_data = student_efficiency(@homework, @work) end + # 实训作品的评阅 + def shixun_work_comment + tip_exception("评阅不能为空") if params[:comment].blank? + tip_exception("缺少is_hidden参数") if params[:is_hidden].blank? || ![true, false].include?(params[:is_hidden]) + comment = @work.student_works_scores.shixun_comment.first || StudentWorksScore.new(student_work_id: @work.id, user_id: current_user.id) + comment.comment = params[:comment] + comment.is_hidden = params[:is_hidden] + comment.save! + normal_status("评阅成功") + end + + # 删除实训作品评阅 + def destroy_work_comment + @work.student_works_scores.shixun_comment.first.destroy! if @work.student_works_scores.shixun_comment.first.present? + normal_status("删除成功") + end + def export_shixun_work_report @user = @work.user @shixun = @homework.shixuns.take diff --git a/app/helpers/admins/base_helper.rb b/app/helpers/admins/base_helper.rb index c655be2e7..0da80f5f6 100644 --- a/app/helpers/admins/base_helper.rb +++ b/app/helpers/admins/base_helper.rb @@ -18,7 +18,7 @@ module Admins::BaseHelper def sidebar_item(url, text, **opts) content = link_to url, 'data-controller': opts[:controller] do - content_tag(:i, '', class: "fa fa-#{opts[:icon]}", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) + + content_tag(:i, '', class: "fa fa-#{opts[:icon]} fa-fw", 'data-toggle': 'tooltip', 'data-placement': 'right', 'data-boundary': 'window', title: text) + content_tag(:span, text) end @@ -87,13 +87,17 @@ module Admins::BaseHelper raw link_to(name, url, { method: :post, remote: true, class: klass, 'data-confirm': '确认审核通过?'}.merge(opts)) end - def delete_link(name, url, **opts) + def delete_link(name, url, **opts, &block) klass = ['action delete-action', opts.delete(:class)].compact.join(' ') refresh_url_data = "refresh_url=#{CGI::escape(request.fullpath)}" url = url + (url.index('?') ? '&' : '?') + refresh_url_data - raw link_to(name, url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts)) + if block_given? + raw link_to(url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts), &block) + else + raw link_to(name, url, { method: :delete, remote: true, class: klass, 'data-confirm': '确认删除?'}.merge(opts)) + end end def unsafe_params diff --git a/app/libs/util/file_manage.rb b/app/libs/util/file_manage.rb index 620fd7f01..f592ca4e7 100644 --- a/app/libs/util/file_manage.rb +++ b/app/libs/util/file_manage.rb @@ -10,10 +10,18 @@ module Util::FileManage File.join(Rails.root, "public", "images", relative_path) end - def disk_filename(source_type,source_id,image_file=nil) + def disk_filename(source_type, source_id,image_file=nil) File.join(storage_path, "#{source_type}", "#{source_id}") end + def exist?(source_type, source_id) + File.exist?(disk_filename(source_type, source_id)) + end + + def disk_file_url(source_type, source_id) + File.join('/images', relative_path, "#{source_type}", "#{source_id}") + end + def disk_auth_filename(source_type, source_id, type) File.join(storage_path, "#{source_type}", "#{source_id}#{type}") end diff --git a/app/models/coo_img.rb b/app/models/coo_img.rb new file mode 100644 index 000000000..0766c0727 --- /dev/null +++ b/app/models/coo_img.rb @@ -0,0 +1,5 @@ +class CooImg < ApplicationRecord + extend Enumerize + + enumerize :img_type, in: %i[com_coop edu_coop alliance_coop] +end \ No newline at end of file diff --git a/app/models/cooperation.rb b/app/models/cooperation.rb new file mode 100644 index 000000000..f0eb30cf6 --- /dev/null +++ b/app/models/cooperation.rb @@ -0,0 +1,9 @@ +class Cooperation < ApplicationRecord + def user_type_text + case user_type.to_i + when 1 then '高校合作' + when 2 then '企业合作' + when 3 then '实训投稿' + end + end +end \ No newline at end of file diff --git a/app/models/help.rb b/app/models/help.rb new file mode 100644 index 000000000..2b8bf5813 --- /dev/null +++ b/app/models/help.rb @@ -0,0 +1,3 @@ +class Help < ApplicationRecord + +end \ No newline at end of file diff --git a/app/models/student_works_score.rb b/app/models/student_works_score.rb index 19043f7f8..86d393f93 100644 --- a/app/models/student_works_score.rb +++ b/app/models/student_works_score.rb @@ -9,6 +9,9 @@ belongs_to :student_work validates :comment, length: { maximum: 2000 } + scope :shixun_comment, lambda { where(is_ultimate: 0) } + + def show_name identity, user identity < Course::STUDENT || self.user == user || self.reviewer_role != 3 end diff --git a/app/services/admins/drag_cooperative_service.rb b/app/services/admins/drag_cooperative_service.rb new file mode 100644 index 000000000..241b7eb11 --- /dev/null +++ b/app/services/admins/drag_cooperative_service.rb @@ -0,0 +1,35 @@ +class Admins::DragCooperativeService < ApplicationService + Error = Class.new(StandardError) + + attr_reader :move, :after + + def initialize(move, after) + @move = move + @after = after # 移动后下一个位置的元素 + end + + def call + return if move.position + 1 == after&.position # 未移动 + raise Error, '未知错误' if after && move.img_type != after.img_type + + coo_imgs = CooImg.where(img_type: move.img_type) + + ActiveRecord::Base.transaction do + if after.blank? # 移动至末尾 + total = coo_imgs.count + + coo_imgs.where('position > ?', move.position).update_all('position = position - 1') + move.update!(position: total) + return + end + + if move.position > after.position # 前移 + coo_imgs.where('position >= ? AND position < ?', after.position, move.position).update_all('position = position + 1') + move.update!(position: after.position) + else # 后移 + coo_imgs.where('position > ? AND position <= ?', move.position, after.position).update_all('position = position - 1') + move.update!(position: after.position) + end + end + end +end \ No newline at end of file diff --git a/app/services/admins/update_user_service.rb b/app/services/admins/update_user_service.rb index 9531d3718..c4755a1ae 100644 --- a/app/services/admins/update_user_service.rb +++ b/app/services/admins/update_user_service.rb @@ -11,7 +11,7 @@ class Admins::UpdateUserService < ApplicationService def call user.assign_attributes(user_attributes) user.firstname = '' - user.password = password if params[:password].present? + user.password = params[:password] if params[:password].present? if params[:identity].to_s == 'student' params[:technical_title] = nil diff --git a/app/services/users/question_bank_service.rb b/app/services/users/question_bank_service.rb index 7e640c6a5..66dff2117 100644 --- a/app/services/users/question_bank_service.rb +++ b/app/services/users/question_bank_service.rb @@ -85,7 +85,7 @@ class Users::QuestionBankService def custom_sort(relations, sort_by, sort_direction) case sort_by when 'updated_at' then - relations.order(updated_at: sort_direction) + relations.order("updated_at #{sort_direction}, id desc") when 'name' then relations.order("CONVERT(name USING gbk) COLLATE gbk_chinese_ci #{sort_direction}") when 'contributor' then diff --git a/app/views/admins/abouts/edit.html.erb b/app/views/admins/abouts/edit.html.erb new file mode 100644 index 000000000..4e75940c2 --- /dev/null +++ b/app/views/admins/abouts/edit.html.erb @@ -0,0 +1,15 @@ +<% define_admin_breadcrumbs do %> + <% add_admin_breadcrumb('关于我们') %> +<% end %> + +
" + this.lang.description + "
", + "", + "", + "" + this.atLink(this.emoji(text)) + "
\n" ); + }; + + markedRenderer.code = function (code, lang, escaped) { + + if (lang === "seq" || lang === "sequence") + { + return "" + code + "
"; + } + else + { + + return marked.Renderer.prototype.code.apply(this, arguments); + } + }; + + markedRenderer.tablecell = function(content, flags) { + var type = (flags.header) ? "th" : "td"; + var tag = (flags.align) ? "<" + type +" style=\"text-align:" + flags.align + "\">" : "<" + type + ">"; + + return tag + this.atLink(this.emoji(content)) + "" + type + ">\n"; + }; + + markedRenderer.listitem = function(text) { + if (settings.taskList && /^\s*\[[x\s]\]\s*/.test(text)) + { + text = text.replace(/^\s*\[\s\]\s*/, " ") + .replace(/^\s*\[x\]\s*/, " "); + + return "数据 | 失败原因 |
---|---|
' + item.data + ' | ' + item.message + ' |
选项
@@ -3223,16 +3209,17 @@ class PollNewQuestbank extends Component {
}}
onInput={(e) => this.HandleInputChanges(e, indexo, indext)}>
{this.state.polls_status === undefined || this.state.polls_status === 1 ?
- (itemo.question.question_type === 2 && itemo.question.answers.length === 3 ?
- (indext ===1?
- this.Ewoption(itemo.question.id, itemo)}>
this.HandleInputChanges(e, indexo, indext)}>
{this.state.polls_status === undefined || this.state.polls_status === 1 ?
- ( itemo.question.question_type === 2 && itemo.question.answers.length === 3 ?
- (indext ===1?
- this.Ewoption(itemo.question.id, itemo)}>
this.Addanotheroption(itemo.question.id)}>添加[其它]选项
+ this.Addanotheroption(itemo.question.id)}>添加[其它]选项
: "")
: ""}
@@ -3960,6 +3955,13 @@ class PollNewQuestbank extends Component {
//////////////////////////////////////////// 可选
" + this.lang.description + " " + this.atLink(this.emoji(text)) + " " + code + "
Open source online Markdown editor.",
+ tocTitle : "目录",
+ toolbar : {
+ undo : "撤销(Ctrl+Z)",
+ redo : "重做(Ctrl+Y)",
+ bold : "粗体",
+ del : "删除线",
+ italic : "斜体",
+ quote : "引用",
+ ucwords : "将每个单词首字母转成大写",
+ uppercase : "将所选转换成大写",
+ lowercase : "将所选转换成小写",
+ h1 : "标题1",
+ h2 : "标题2",
+ h3 : "标题3",
+ h4 : "标题4",
+ h5 : "标题5",
+ h6 : "标题6",
+ "list-ul" : "无序列表",
+ "list-ol" : "有序列表",
+ hr : "横线",
+ link : "链接",
+ "reference-link" : "引用链接",
+ image : "添加图片",
+ code : "行内代码",
+ "preformatted-text" : "预格式文本 / 代码块(缩进风格)",
+ "code-block" : "代码块(多语言风格)",
+ table : "添加表格",
+ datetime : "日期时间",
+ emoji : "Emoji表情",
+ "html-entities" : "HTML实体字符",
+ pagebreak : "插入分页符",
+ "goto-line" : "跳转到行",
+ watch : "关闭实时预览",
+ unwatch : "开启实时预览",
+ preview : "全窗口预览HTML(按 Shift + ESC还原)",
+ fullscreen : "全屏(按ESC还原)",
+ clear : "清空",
+ search : "搜索",
+ help : "使用帮助",
+ info : "关于" + editormd.title
+ },
+ buttons : {
+ enter : "确定",
+ cancel : "取消",
+ close : "关闭"
+ },
+ dialog : {
+ link : {
+ title : "添加链接",
+ url : "链接地址",
+ urlTitle : "链接标题",
+ urlEmpty : "错误:请填写链接地址。"
+ },
+ referenceLink : {
+ title : "添加引用链接",
+ name : "引用名称",
+ url : "链接地址",
+ urlId : "链接ID",
+ urlTitle : "链接标题",
+ nameEmpty: "错误:引用链接的名称不能为空。",
+ idEmpty : "错误:请填写引用链接的ID。",
+ urlEmpty : "错误:请填写引用链接的URL地址。"
+ },
+ image : {
+ title : "添加图片",
+ url : "图片地址",
+ link : "图片链接",
+ alt : "图片描述",
+ uploadButton : "本地上传",
+ imageURLEmpty : "错误:图片地址不能为空。",
+ uploadFileEmpty : "错误:上传的图片不能为空。",
+ formatNotAllowed : "错误:只允许上传图片文件,允许上传的图片文件格式有:"
+ },
+ preformattedText : {
+ title : "添加预格式文本或代码块",
+ emptyAlert : "错误:请填写预格式文本或代码的内容。"
+ },
+ codeBlock : {
+ title : "添加代码块",
+ selectLabel : "代码语言:",
+ selectDefaultText : "请选择代码语言",
+ otherLanguage : "其他语言",
+ unselectedLanguageAlert : "错误:请选择代码所属的语言类型。",
+ codeEmptyAlert : "错误:请填写代码内容。"
+ },
+ htmlEntities : {
+ title : "HTML 实体字符"
+ },
+ help : {
+ title : "使用帮助"
+ }
+ }
+ }
+ };
+
+ editormd.classNames = {
+ tex : editormd.classPrefix + "tex"
+ };
+
+ editormd.dialogZindex = 99999;
+
+ editormd.$katex = null;
+ editormd.$marked = null;
+ editormd.$CodeMirror = null;
+ editormd.$prettyPrint = null;
+
+ var timer, flowchartTimer;
+
+ editormd.prototype = editormd.fn = {
+ state : {
+ watching : false,
+ loaded : false,
+ preview : false,
+ fullscreen : false
+ },
+
+ /**
+ * 构造函数/实例初始化
+ * Constructor / instance initialization
+ *
+ * @param {String} id 编辑器的ID
+ * @param {Object} [options={}] 配置选项 Key/Value
+ * @returns {editormd} 返回editormd的实例对象
+ */
+
+ init : function (id, options) {
+
+ options = options || {};
+
+ if (typeof id === "object")
+ {
+ options = id;
+ }
+
+ var _this = this;
+ var classPrefix = this.classPrefix = editormd.classPrefix;
+ var settings = this.settings = $.extend(true, editormd.defaults, options);
+
+ id = (typeof id === "object") ? settings.id : id;
+
+ var editor = this.editor = $("#" + id);
+
+ this.id = id;
+ this.lang = settings.lang;
+
+ var classNames = this.classNames = {
+ textarea : {
+ html : classPrefix + "html-textarea",
+ markdown : classPrefix + "markdown-textarea"
+ }
+ };
+
+ settings.pluginPath = (settings.pluginPath === "") ? settings.path + "../plugins/" : settings.pluginPath;
+
+ this.state.watching = (settings.watch) ? true : false;
+
+ if ( !editor.hasClass("editormd") ) {
+ editor.addClass("editormd");
+ }
+
+ editor.css({
+ width : (typeof settings.width === "number") ? settings.width + "px" : settings.width,
+ height : (typeof settings.height === "number") ? settings.height + "px" : settings.height
+ });
+
+ if (settings.autoHeight)
+ {
+ editor.css("height", "auto");
+ }
+
+ var markdownTextarea = this.markdownTextarea = editor.children("textarea");
+
+ if (markdownTextarea.length < 1)
+ {
+ editor.append("");
+ markdownTextarea = this.markdownTextarea = editor.children("textarea");
+ }
+
+ markdownTextarea.addClass(classNames.textarea.markdown).attr("placeholder", settings.placeholder);
+
+ if (typeof markdownTextarea.attr("name") === "undefined" || markdownTextarea.attr("name") === "")
+ {
+ markdownTextarea.attr("name", (settings.name !== "") ? settings.name : id + "-markdown-doc");
+ }
+
+ var appendElements = [
+ (!settings.readOnly) ? "" : "",
+ ( (settings.saveHTMLToTextarea) ? "" : "" ),
+ " " + editormd.title + "v" + editormd.version + "
",
+ "";
+ }
+ }
+ else
+ {
+ var src = (name === "+1") ? "plus1" : name;
+ src = (src === "black_large_square") ? "black_square" : src;
+ src = (src === "moon") ? "waxing_gibbous_moon" : src;
+
+ return "
";
+ }
+ }
+ });
+ }
+
+ return text;
+ };
+
+ markedRenderer.atLink = function(text) {
+
+ if (atLinkReg.test(text))
+ {
+ if (settings.atLink)
+ {
+ text = text.replace(emailReg, function($1, $2, $3, $4) {
+ return $1.replace(/@/g, "_#_@_#_");
+ });
+
+ text = text.replace(atLinkReg, function($1, $2) {
+ return "" + $1 + "";
+ }).replace(/_#_@_#_/g, "@");
+ }
+
+ if (settings.emailLink)
+ {
+ text = text.replace(emailLinkReg, function($1, $2, $3, $4, $5) {
+ return (!$2 && $.inArray($5, "jpg|jpeg|png|gif|webp|ico|icon|pdf".split("|")) < 0) ? ""+$1+"" : $1;
+ });
+ }
+
+ return text;
+ }
+
+ return text;
+ };
+
+ markedRenderer.link = function (href, title, text) {
+
+ if (this.options.sanitize) {
+ try {
+ var prot = decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase();
+ } catch(e) {
+ return "";
+ }
+
+ if (prot.indexOf("javascript:") === 0) {
+ return "";
+ }
+ }
+
+ var out = "" + text.replace(/@/g, "@") + "";
+ }
+
+ if (title) {
+ out += " title=\"" + title + "\"";
+ }
+
+ out += ">" + text + "
";
+ }
+
+ return text;
+ };
+
+ markedRenderer.paragraph = function(text) {
+ var isTeXInline = /\$\$(.*)\$\$/g.test(text);
+ var isTeXLine = /^\$\$(.*)\$\$$/.test(text);
+ var isTeXAddClass = (isTeXLine) ? " class=\"" + editormd.classNames.tex + "\"" : "";
+ var isToC = (settings.tocm) ? /^(\[TOC\]|\[TOCM\])$/.test(text) : /^\[TOC\]$/.test(text);
+ var isToCMenu = /^\[TOCM\]$/.test(text);
+
+ if (!isTeXLine && isTeXInline)
+ {
+ text = text.replace(/(\$\$([^\$]*)\$\$)+/g, function($1, $2) {
+ return "" + $2.replace(/\$/g, "") + "";
+ });
+ }
+ else
+ {
+ text = (isTeXLine) ? text.replace(/\$/g, "") : text;
+ }
+
+ var tocHTML = "
" : tocHTML )
+ : ( (pageBreakReg.test(text)) ? this.pageBreak(text) : "";
+ lastLevel = level;
+ }
+
+ var tocContainer = container.find(".markdown-toc");
+
+ if ((tocContainer.length < 1 && container.attr("previewContainer") === "false"))
+ {
+ var tocHTML = "";
+
+ tocHTML = (tocDropdown) ? "
");
+ }
+
+ tocContainer.html("").children(".markdown-toc-list").html(html.replace(/\r?\n?\
\<\/ul\>/g, ""));
+
+ return tocContainer;
+ };
+
+ /**
+ *
+ * 生成TOC下拉菜单
+ * Creating ToC dropdown menu
+ *
+ * @param {Object} container 插入TOC的容器jQuery对象元素
+ * @param {String} tocTitle ToC title
+ * @returns {Object} return toc-menu object
+ */
+
+ editormd.tocDropdownMenu = function(container, tocTitle) {
+
+ tocTitle = tocTitle || "Table of Contents";
+
+ var zindex = 400;
+ var tocMenus = container.find("." + this.classPrefix + "toc-menu");
+
+ tocMenus.each(function() {
+ var $this = $(this);
+ var toc = $this.children(".markdown-toc");
+ var icon = "";
+ var btn = "" + icon + tocTitle + "";
+ var menu = toc.children("ul");
+ var list = menu.find("li");
+
+ toc.append(btn);
+
+ list.first().before("
" + tocTitle + " " + icon + "
Open source online Markdown editor.",
+ tocTitle : "目錄",
+ toolbar : {
+ undo : "撤銷(Ctrl+Z)",
+ redo : "重做(Ctrl+Y)",
+ bold : "粗體",
+ del : "刪除線",
+ italic : "斜體",
+ quote : "引用",
+ ucwords : "將所選的每個單詞首字母轉成大寫",
+ uppercase : "將所選文本轉成大寫",
+ lowercase : "將所選文本轉成小寫",
+ h1 : "標題1",
+ h2 : "標題2",
+ h3 : "標題3",
+ h4 : "標題4",
+ h5 : "標題5",
+ h6 : "標題6",
+ "list-ul" : "無序列表",
+ "list-ol" : "有序列表",
+ hr : "横线",
+ link : "链接",
+ "reference-link" : "引用鏈接",
+ image : "圖片",
+ code : "行內代碼",
+ "preformatted-text" : "預格式文本 / 代碼塊(縮進風格)",
+ "code-block" : "代碼塊(多語言風格)",
+ table : "添加表格",
+ datetime : "日期時間",
+ emoji : "Emoji 表情",
+ "html-entities" : "HTML 實體字符",
+ pagebreak : "插入分頁符",
+ watch : "關閉實時預覽",
+ unwatch : "開啟實時預覽",
+ preview : "全窗口預覽HTML(按 Shift + ESC 退出)",
+ fullscreen : "全屏(按 ESC 退出)",
+ clear : "清空",
+ search : "搜尋",
+ help : "使用幫助",
+ info : "關於" + exports.title
+ },
+ buttons : {
+ enter : "確定",
+ cancel : "取消",
+ close : "關閉"
+ },
+ dialog : {
+ link : {
+ title : "添加鏈接",
+ url : "鏈接地址",
+ urlTitle : "鏈接標題",
+ urlEmpty : "錯誤:請填寫鏈接地址。"
+ },
+ referenceLink : {
+ title : "添加引用鏈接",
+ name : "引用名稱",
+ url : "鏈接地址",
+ urlId : "鏈接ID",
+ urlTitle : "鏈接標題",
+ nameEmpty: "錯誤:引用鏈接的名稱不能為空。",
+ idEmpty : "錯誤:請填寫引用鏈接的ID。",
+ urlEmpty : "錯誤:請填寫引用鏈接的URL地址。"
+ },
+ image : {
+ title : "添加圖片",
+ url : "圖片地址",
+ link : "圖片鏈接",
+ alt : "圖片描述",
+ uploadButton : "本地上傳",
+ imageURLEmpty : "錯誤:圖片地址不能為空。",
+ uploadFileEmpty : "錯誤:上傳的圖片不能為空!",
+ formatNotAllowed : "錯誤:只允許上傳圖片文件,允許上傳的圖片文件格式有:"
+ },
+ preformattedText : {
+ title : "添加預格式文本或代碼塊",
+ emptyAlert : "錯誤:請填寫預格式文本或代碼的內容。"
+ },
+ codeBlock : {
+ title : "添加代碼塊",
+ selectLabel : "代碼語言:",
+ selectDefaultText : "請語言代碼語言",
+ otherLanguage : "其他語言",
+ unselectedLanguageAlert : "錯誤:請選擇代碼所屬的語言類型。",
+ codeEmptyAlert : "錯誤:請填寫代碼內容。"
+ },
+ htmlEntities : {
+ title : "HTML實體字符"
+ },
+ help : {
+ title : "使用幫助"
+ }
+ }
+ };
+
+ exports.defaults.lang = lang;
+ };
+
+ // CommonJS/Node.js
+ if (typeof require === "function" && typeof exports === "object" && typeof module === "object")
+ {
+ module.exports = factory;
+ }
+ else if (typeof define === "function") // AMD/CMD/Sea.js
+ {
+ if (define.amd) { // for Require.js
+
+ define(["editormd"], function(editormd) {
+ factory(editormd);
+ });
+
+ } else { // for Sea.js
+ define(function(require) {
+ var editormd = require("../editormd");
+ factory(editormd);
+ });
+ }
+ }
+ else
+ {
+ factory(window.editormd);
+ }
+
+})();
\ No newline at end of file