Merge branch 'dev_aliyun' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun

dev_hjm_a
SylorHuang 5 years ago
commit 2aa630e911

@ -96,3 +96,5 @@ gem 'searchkick'
gem 'aasm'
gem 'enumerize'
gem 'diffy'

@ -98,6 +98,7 @@ GEM
connection_pool (2.2.2)
crass (1.0.4)
diff-lcs (1.3)
diffy (3.3.0)
elasticsearch (7.2.0)
elasticsearch-api (= 7.2.0)
elasticsearch-transport (= 7.2.0)
@ -368,6 +369,7 @@ DEPENDENCIES
byebug
capybara (>= 2.15, < 4.0)
chromedriver-helper
diffy
enumerize
faraday (~> 0.15.4)
font-awesome-sass (= 4.7.0)

@ -0,0 +1,125 @@
$(document).on('turbolinks:load', function() {
if ($('body.admins-carousels-index-page').length > 0) {
// ------------ 保存链接 -----------
$('.carousels-card').on('click', '.save-data-btn', function(){
var $link = $(this);
var id = $link.data('id');
var link = $('.custom-carousel-item-' + id).find('.link-input').val();
var name = $('.custom-carousel-item-' + id).find('.name-input').val();
if(!name || name.length == 0){
$.notify({ message: '名称不能为空' },{ type: 'danger' });
return;
}
$link.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { link: link, name: name },
success: function(data){
$.notify({ message: '操作成功' });
},
error: ajaxErrorNotifyHandler,
complete: function(){
$link.removeAttr('disabled');
}
})
});
// -------------- 是否在首页展示 --------------
$('.carousels-card').on('change', '.online-check-box', function(){
var $checkbox = $(this);
var id = $checkbox.data('id');
var checked = $checkbox.is(':checked');
$checkbox.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { status: checked },
success: function(data){
$.notify({ message: '保存成功' });
var box = $('.custom-carousel-item-' + id).find('.drag');
if(checked){
box.removeClass('not_active');
}else{
box.addClass('not_active');
}
},
error: ajaxErrorNotifyHandler,
complete: function(){
$checkbox.removeAttr('disabled');
}
})
});
// ------------ 拖拽 -------------
var onDropFunc = function(el, _target, _source, sibling){
var moveId = $(el).data('id');
var insertId = $(sibling).data('id') || '';
$.ajax({
url: '/admins/carousels/drag',
method: 'POST',
dataType: 'json',
data: { move_id: moveId, after_id: insertId },
success: function(data){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
},
error: function(res){
var data = res.responseJSON;
$.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'});
}
})
};
var ele1 = document.getElementById('carousels-container');
dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc);
// ----------- 新增 --------------
var $createModal = $('.modal.admin-add-carousel-modal');
var $createForm = $createModal.find('form.admin-add-carousel-form');
$createForm.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"portal_image[image]": {
required: true
},
"portal_image[name]": {
required: true
},
}
});
$createModal.on('show.bs.modal', function(event){
resetFileInputFunc($createModal.find('.img-file-input'));
$createModal.find('.file-names').html('选择文件');
});
$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 $carouselItem = $('.custom-carousel-item-' + data.source_id);
$carouselItem.find('.custom-carousel-item-img img').attr('src', data.url);
})
}
})

@ -0,0 +1,60 @@
.admins-carousels-index-page {
.carousels-card {
.custom-carousel-item {
& > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
&-no {
font-size: 28px;
text-align: center;
}
&-img {
cursor: pointer;
width: 100%;
height: 60px;
& > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
}
.not_active {
background: #F0F0F0;
}
.delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
.save-url-btn {
cursor: pointer;
}
.operate-box {
display: flex;
justify-content: space-between;
align-items: center;
}
.online-check-box {
font-size: 20px;
}
.name-input {
flex: 1;
}
.link-input {
flex: 3;
}
}
}
}

@ -33,7 +33,7 @@ class AccountsController < ApplicationController
uid_logger("start register: verifi_code is #{verifi_code}, code is #{code}, time is #{Time.now.to_i - verifi_code.try(:created_at).to_i}")
# check_code = (verifi_code.try(:code) == code.strip && (Time.now.to_i - verifi_code.created_at.to_i) <= 10*60)
# todo 上线前请删除万能验证码"513231"
unless code == "513231" && request.subdomain == "pre-newweb"
unless code == "513231" && request.subdomain == "test-newweb"
return normal_status(-2, "验证码不正确") if verifi_code.try(:code) != code.strip
return normal_status(-2, "验证码已失效") if !verifi_code&.effective?
end

@ -0,0 +1,80 @@
class Admins::CarouselsController < Admins::BaseController
before_action :convert_file!, only: [:create]
def index
@images = PortalImage.order(position: :asc)
end
def create
position = PortalImage.count + 1
ActiveRecord::Base.transaction do
image = PortalImage.create!(create_params.merge(position: position))
file_path = Util::FileManage.disk_filename('PortalImage', image.id)
File.delete(file_path) if File.exist?(file_path) # 删除之前的文件
Util.write_file(@file, file_path)
end
flash[:success] = '保存成功'
redirect_to admins_carousels_path
end
def update
current_image.update!(update_params)
render_ok
end
def destroy
ActiveRecord::Base.transaction do
current_image.destroy!
# 前移
PortalImage.where('position > ?', current_image.position)
.update_all('position = position - 1')
file_path = Util::FileManage.disk_filename('PortalImage', current_image.id)
File.delete(file_path) if File.exist?(file_path)
end
render_delete_success
end
def drag
move = PortalImage.find_by(id: params[:move_id])
after = PortalImage.find_by(id: params[:after_id])
Admins::DragPortalImageService.call(move, after)
render_ok
rescue Admins::DragPortalImageService::Error => e
render_error(e.message)
end
private
def current_image
@_current_image ||= PortalImage.find(params[:id])
end
def create_params
params.require(:portal_image).permit(:name, :link)
end
def update_params
params.permit(:name, :link, :status)
end
def convert_file!
max_size = 10 * 1024 * 1024 # 10M
file = params.dig('portal_image', '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

@ -355,7 +355,7 @@ class ApplicationController < ActionController::Base
# type 0 创始内容, 1 最新内容
def game_passed_code(path, myshixun, game_id)
# 如果代码窗口是隐藏的,则不用保存代码
return if myshixun.shixun.hide_code
return if myshixun.shixun.hide_code || myshixun.shixun.vnc
file_content = git_fle_content myshixun.repo_path, path
unless file_content.present?
raise("获取文件代码异常")

@ -1,17 +1,22 @@
class Ecs::CourseAchievementMethodsController < Ecs::CourseBaseController
def show
include_associations = { ec_course_achievement_methods: [:ec_course_evaluation, :ec_course_evaluation_subitems] }
include_associations = { ec_course_achievement_methods:
[:ec_course_evaluation, ec_achievement_evaluation_relates: :ec_course_evaluation_subitem] }
@course_targets = current_course.ec_course_targets.includes(include_associations)
end
def create
@course_target = Ecs::CreateCourseAchievementMethodsService.call(current_course_target, create_params)
Ecs::CreateCourseAchievementMethodsService.call(current_course_target, create_params)
render_ok
end
private
def create_params
params.permit(course_achievement_methods: %i[id course_evaluation_id course_evaluation_subitem_ids score percentage])
params.permit(course_achievement_methods: [
:id, :course_evaluation_id, :score, :percentage,
course_evaluation_relates: %i[:subitem_id position]
])
end
def current_course_target

@ -3,7 +3,8 @@ class Ecs::CourseManagersController < Ecs::CourseBaseController
before_action :check_major_manager_permission!, only: [:create, :destroy]
def create
@user = Ecs::CreateCourseManagerService.call(current_course, params[:user_id])
Ecs::CreateCourseManagerService.call(current_course, params[:user_ids])
render_ok
rescue Ecs::CreateCourseManagerService::Error => ex
render_error(ex.message)
end

@ -20,8 +20,8 @@ class Ecs::CourseTargetsController < Ecs::CourseBaseController
def with_achievement_methods
@course_targets = current_course.ec_course_targets
.includes(:ec_graduation_subitems,
ec_course_achievement_methods: [:ec_course_evaluation, :ec_course_evaluation_subitems])
.includes(ec_course_achievement_methods:
[:ec_course_evaluation, ec_achievement_evaluation_relates: :ec_course_evaluation_subitem])
end
private

@ -5,8 +5,9 @@ class Ecs::EvaluationsController < Ecs::CourseBaseController
render_ok(
course_targets: service.course_targets,
course_achievement: service.course_achievement,
course_rate: service.course_rate,
graduation_subitem_evaluations: service.graduation_subitem_evaluations,
score_levels_map: service.score_levels_map
score_levels: service.score_levels
)
end

@ -46,8 +46,8 @@ class ExercisesController < ApplicationController
@exercises = member_show_exercises.exists? ? member_show_exercises.unified_setting : []
else #已分班级的成员,可以查看统一设置和单独设置(试卷是发布在该班级)试卷
# 已发布 当前用户班级分组的 试卷id
not_exercise_ids = @course.exercise_group_settings.exercise_group_not_published.where("course_group_id = #{@member_group_id}").pluck(:exercise_id)
@exercises = member_show_exercises.where.not(id: not_exercise_ids)
publish_exercise_ids = @course.exercise_group_settings.exercise_group_published.where("course_group_id = #{@member_group_id}").pluck(:exercise_id)
@exercises = member_show_exercises.unified_setting.or(member_show_exercises.where(id: publish_exercise_ids))
end
else #用户未登陆或不是该课堂成员,仅显示统一设置的(已发布的/已截止的),如有公开,则不显示锁,不公开,则显示锁
@is_teacher_or = 0

@ -657,7 +657,8 @@ class GamesController < ApplicationController
# 高性能取上一关、下一关
prev_game = @game.prev_of_current_game(@shixun.id, @game.myshixun_id, game_challenge.position)
next_game = @game.next_of_current_game(@shixun.id, @game.myshixun_id, game_challenge.position) if had_passed
next_game = @game.next_game(@shixun.id, @game.myshixun_id, game_challenge.position) if had_passed
next_game.update_column(:status, 0) if next_game.present? && next_game.status == 3
# 高性能取上一关、下一关
#prev_game = Game.prev_identifier(@shixun.id, @game.myshixun_id, game_challenge.position)
@ -670,7 +671,7 @@ class GamesController < ApplicationController
choose_correct_num: choose_correct_num,
test_sets: test_sets,
prev_game: prev_game,
next_game: next_game}
next_game: next_game&.identifier}
rescue Exception => e
uid_logger("choose build failed #{e.message}")
@result = [status: -1, contents: "#{e.message}"]

@ -5,7 +5,7 @@ class HomeController < ApplicationController
images = PortalImage.where(status: true).order("position asc")
@images_url = []
images.each do |image|
@images_url << {path: image.link, image_url: "/images/avatars/PortalImage/#{image.id}"}
@images_url << {path: image.link, image_url: Util::FileManage.disk_file_url('PortalImage', image.id)}
end
# 目录分级

@ -201,12 +201,12 @@ class HomeworkCommonsController < ApplicationController
page = params[:page] || 1
limit = params[:limit] || 20
@student_works = @student_works.page(page).per(limit)
@students = @course.students.where(user_id: @student_works.pluck(:user_id)).preload(:course_group)
if @homework.homework_type == "practice"
@student_works = @student_works.includes(:student_works_scores, user: :user_extension, myshixun: :games)
else
@student_works = @student_works.includes(:student_works_scores, :project, user: :user_extension)
end
@students = @course.students.preload(:course_group)
end
if params[:format] == "xlsx"

@ -25,9 +25,9 @@ class MemosController < ApplicationController
!search.blank? ? "forum_id = #{forum_id} and root_id is null and subject like '%#{search}%'" :
"forum_id = #{forum_id} and root_id is null"
elsif !search.blank?
"forum_id in(3, 5) and root_id is null and subject like '%#{search}%'"
"forum_id in(3, 5, 16) and root_id is null and subject like '%#{search}%'"
else
"forum_id in(3, 5) and root_id is null"
"forum_id in(3, 5, 16) and root_id is null"
end
if tag_repertoire_id

@ -326,7 +326,7 @@ class MyshixunsController < ApplicationController
if edu_css.present?
css_path = edu_css.split(",")
css_path.each do |path|
file_content = GitService.file_content(repo_path: @repo_path, path: path)["content"]
file_content = git_fle_content(@repo_path, path)["content"]
file_content = tran_base64_decode64(file_content) unless file_content.blank?
@contents = @contents.sub(/EDUCODERCSS/, "<style>#{file_content}</style>")
end
@ -335,7 +335,7 @@ class MyshixunsController < ApplicationController
if edu_js.present?
js_path = edu_js.split(",")
js_path.each do |path|
file_content = GitService.file_content(repo_path: @repo_path, path: path)["content"]
file_content = git_fle_content(@repo_path, path)["content"]
file_content = tran_base64_decode64(file_content) unless file_content.blank?
@contents = @contents.sub(/EDUCODERJS/, "<script>#{file_content}</script>")
end

@ -692,7 +692,7 @@ class PollsController < ApplicationController
else
unified_setting = @poll.unified_setting
end
show_result = params[:show_result].to_i
show_result = params[:show_result]
un_anonymous = params[:un_anonymous] ? true : false
# 统一设置或者分班为0则更新问卷并删除问卷分组
if unified_setting || (course_group_ids.size == 0)
@ -1318,7 +1318,7 @@ class PollsController < ApplicationController
poll_ques_titles = poll_questions.pluck(:question_title).map {|k| ActionController::Base.helpers.strip_tags(k) if k.present?}
poll_un_anony = poll.un_anonymous
if poll_un_anony #是否匿名默认为false
user_info = %w(登陆名 真实姓名 邮箱 学号)
user_info = %w(登陆名 真实姓名 邮箱 学号 学员单位)
else
user_info = []
end
@ -1392,7 +1392,8 @@ class PollsController < ApplicationController
user_login = u_user.login
user_name = u_user.real_name.present? ? u_user.real_name : "--"
user_student_id = u_user.student_id.present? ? u_user.student_id : "--"
user_cell += [user_login,user_name, u_user.mail, user_student_id]
user_school_name = u_user.school_name.present? ? u_user.school_name : "--"
user_cell += [user_login,user_name, u_user.mail, user_student_id, user_school_name]
end
all_user_cell = user_cell + user_answer_array
user_commit.push(all_user_cell)

@ -2,6 +2,7 @@ class ShixunsController < ApplicationController
include ShixunsHelper
include ApplicationHelper
include ElasticsearchAble
include CoursesHelper
before_action :require_login, :check_auth, except: [:download_file, :index, :menus, :show, :show_right, :ranking_list,
:discusses, :collaborators, :fork_list, :propaedeutics]

@ -0,0 +1,5 @@
class TemplatesController < ApplicationController
def show
@template = EcTemplate.find_by_name!(params[:name])
end
end

@ -2,10 +2,10 @@ class Wechats::JsSdkSignaturesController < ApplicationController
def create
timestamp = Time.now.to_i
noncestr = ('A'..'z').to_a.sample(8).join
signature = Util::Wechat.js_sdk_signature(params[:url], noncestr, timestamp)
signature = Wechat::OfficialAccount.js_sdk_signature(params[:url], noncestr, timestamp)
render_ok(appid: Util::Wechat.appid, timestamp: timestamp, noncestr: noncestr, signature: signature)
rescue Util::Wechat::Error => ex
render_ok(appid: Wechat::OfficialAccount.appid, timestamp: timestamp, noncestr: noncestr, signature: signature)
rescue Wechat::Error => ex
render_error(ex.message)
end
end

@ -4,5 +4,12 @@ class Users::ApplyAuthenticationForm
attr_accessor :name, :id_number, :upload_image
validates :name, presence: true
validates :id_number, presence: true
validate :validate_ID_number
def validate_ID_number
unless id_number =~ User::VALID_NUMBER_REGEX
raise("身份证格式不对")
end
end
end

@ -1,6 +1,6 @@
module MemosHelper
def forum_list
[{id: 5, name: "技术分享"}, {id: 3, name: "操作指南"}]
[{id: 5, name: "技术分享"}, {id: 3, name: "操作指南"}, {id: 16, name: "通知公告"}]
end
end

@ -0,0 +1,12 @@
class CreateDiffRecordJob < ApplicationJob
queue_as :default
def perform(user_id, obj_id, obj_klass, column_name, before, after)
user = User.find_by(id: user_id)
obj = obj_klass.constantize.find_by(id: obj_id)
return if user.blank? || obj.blank?
CreateDiffRecordService.call(user, obj, column_name, before, after)
end
end

@ -22,12 +22,19 @@ module Util::FileManage
File.exist?(disk_filename(source.class, source.id))
end
def disk_file_url(source_type, source_id)
File.join('/images', relative_path, "#{source_type}", "#{source_id}")
def disk_file_url(source_type, source_id, suffix = nil)
t = ctime(source_type, source_id)
File.join('/images', relative_path, "#{source_type}", "#{source_id}#{suffix}") + "?t=#{t}"
end
def source_disk_file_url(source)
File.join('/images', relative_path, "#{source.class}", "#{source.id}")
disk_file_url(source.class, source.id)
end
def ctime(source_type, source_id)
return nil unless exist?(source_type, source_id)
File.ctime(disk_filename(source_type, source_id)).to_i
end
def disk_auth_filename(source_type, source_id, type)
@ -39,7 +46,7 @@ module Util::FileManage
end
def auth_file_url(source_type, source_id, type)
File.join('/images', relative_path, source_type, "#{source_id}#{type}")
disk_file_url(source_type, source_id, type)
end
def real_name_auth_file_url(source_id)

@ -1,75 +0,0 @@
module Util::Wechat
BASE_SITE = 'https://api.weixin.qq.com'.freeze
Error = Class.new(StandardError)
class << self
attr_accessor :appid, :secret
def js_sdk_signature(url, noncestr, timestamp)
data = { jsapi_ticket: jsapi_ticket, noncestr: noncestr, timestamp: timestamp, url: url }
str = data.map { |k, v| "#{k}=#{v}" }.join('&')
Digest::SHA1.hexdigest(str)
end
def access_token
# 7200s 有效时间
Rails.cache.fetch(access_token_cache_key, expires_in: 100.minutes) do
result = request(:get, '/cgi-bin/token', appid: appid, secret: secret, grant_type: 'client_credential')
result['access_token']
end
end
def refresh_access_token
Rails.cache.delete(access_token_cache_key)
access_token
end
def jsapi_ticket
# 7200s 有效时间
Rails.cache.fetch(jsapi_ticket_cache_key, expires_in: 100.minutes) do
result = request(:get, '/cgi-bin/ticket/getticket', access_token: access_token, type: 'jsapi')
result['ticket']
end
end
def refresh_jsapi_ticket
Rails.cache.delete(jsapi_ticket_cache_key)
jsapi_ticket
end
def access_token_cache_key
"#{base_cache_key}/access_token"
end
def jsapi_ticket_cache_key
"#{base_cache_key}/jsapi_ticket"
end
def base_cache_key
"wechat/#{appid}"
end
private
def request(method, url, **params)
Rails.logger.error("[wechat] request: #{method} #{url} #{params.inspect}")
client = Faraday.new(url: BASE_SITE)
response = client.public_send(method, url, params)
result = JSON.parse(response.body)
Rails.logger.error("[wechat] response:#{response.status} #{result.inspect}")
if response.status != 200
raise Error, result.inspect
end
if result['errcode'].present? && result['errcode'].to_i.nonzero?
raise Error, result.inspect
end
result
end
end
end

@ -0,0 +1,2 @@
module Wechat
end

@ -0,0 +1,11 @@
class Wechat::App
class << self
attr_accessor :appid, :secret
delegate :access_token, :jscode2session, to: :client
def client
@_client ||= Wechat::Client.new(appid, secret)
end
end
end

@ -0,0 +1,74 @@
class Wechat::Client
BASE_SITE = 'https://api.weixin.qq.com'.freeze
attr_reader :appid, :secret
def initialize(appid, secret)
@appid = appid
@secret = secret
end
def access_token
# 7200s 有效时间
Rails.cache.fetch(access_token_cache_key, expires_in: 100.minutes) do
result = request(:get, '/cgi-bin/token', appid: appid, secret: secret, grant_type: 'client_credential')
result['access_token']
end
end
def refresh_access_token
Rails.cache.delete(access_token_cache_key)
access_token
end
def jsapi_ticket
# 7200s 有效时间
Rails.cache.fetch(jsapi_ticket_cache_key, expires_in: 100.minutes) do
result = request(:get, '/cgi-bin/ticket/getticket', access_token: access_token, type: 'jsapi')
result['ticket']
end
end
def refresh_jsapi_ticket
Rails.cache.delete(jsapi_ticket_cache_key)
jsapi_ticket
end
def jscode2session(code)
request(:get, '/sns/jscode2session', appid: appid, secret: secret, js_code: code, grant_type: 'authorization_code')
end
def access_token_cache_key
"#{base_cache_key}/access_token"
end
def jsapi_ticket_cache_key
"#{base_cache_key}/jsapi_ticket"
end
def base_cache_key
"wechat/#{appid}"
end
private
def request(method, url, **params)
Rails.logger.error("[wechat] request: #{method} #{url} #{params.except(:secret).inspect}")
client = Faraday.new(url: BASE_SITE)
response = client.public_send(method, url, params)
result = JSON.parse(response.body)
Rails.logger.error("[wechat] response:#{response.status} #{result.inspect}")
if response.status != 200
raise Wechat::Error.parse(result)
end
if result['errcode'].present? && result['errcode'].to_i.nonzero?
raise Wechat::Error.parse(result)
end
result
end
end

@ -0,0 +1,14 @@
class Wechat::Error < StandardError
attr_reader :code
def initialize(code, message)
super message
@code = code
end
class << self
def parse(result)
new(result['errcode'], result['errmsg'])
end
end
end

@ -0,0 +1,17 @@
class Wechat::OfficialAccount
class << self
attr_accessor :appid, :secret
delegate :access_token, :jsapi_ticket, to: :client
def js_sdk_signature(url, noncestr, timestamp)
data = { jsapi_ticket: jsapi_ticket, noncestr: noncestr, timestamp: timestamp, url: url }
str = data.map { |k, v| "#{k}=#{v}" }.join('&')
Digest::SHA1.hexdigest(str)
end
def client
@_client ||= Wechat::Client.new(appid, secret)
end
end
end

@ -28,6 +28,8 @@ class Challenge < ApplicationRecord
scope :fields_for_list, -> { select([:id, :subject, :st, :score, :position, :shixun_id]) }
after_commit :create_diff_record
def next_challenge
position = self.position + 1
Challenge.where(:position => position, :shixun_id => self.shixun).first
@ -126,4 +128,11 @@ class Challenge < ApplicationRecord
end
# 关卡评测文件
private
def create_diff_record
return unless task_pass_previously_changed?
CreateDiffRecordJob.perform_later(User.current.id, id, 'Challenge', 'task_pass', task_pass_before_last_save, task_pass)
end
end

@ -0,0 +1,8 @@
class DiffRecord < ApplicationRecord
belongs_to :user
belongs_to :container, polymorphic: true
has_one :diff_record_content, dependent: :destroy
delegate :content, to: :diff_record_content
end

@ -0,0 +1,3 @@
class DiffRecordContent < ApplicationRecord
belongs_to :diff_record
end

@ -6,7 +6,7 @@ class EcCourse < ApplicationRecord
has_many :ec_graduation_subitem_course_targets, through: :ec_course_targets
# 课程负责教师
has_many :ec_course_users
has_many :course_managers, through: :ec_course_users, class_name: 'User'
has_many :course_managers, through: :ec_course_users, source: :user
# 课程考核标准
has_many :ec_course_evaluations, dependent: :destroy
# 课程等级
@ -21,7 +21,7 @@ class EcCourse < ApplicationRecord
has_many :courses, through: :ec_major_courses
# 课程目标达成方法
# has_many :ec_course_achievement_methods
has_many :ec_course_achievement_methods
accepts_nested_attributes_for :ec_course_targets, :ec_score_levels, allow_destroy: true
end

@ -16,4 +16,23 @@ class EcCourseEvaluation < ApplicationRecord
def imported?
import_status?
end
def evaluation_relates
# 总成绩支撑只有课程考核标准的名称, 分项成绩支撑是课程考核标准名称与考核分项的笛卡尔积
if status == 1
return evaluation_count.times.map { |index| { id: -1, name: "#{name}#{index + 1}", position: index + 1 } }
end
if is_course_type?
ec_course_evaluation_subitems.map.with_index { |item, index| { id: item.id, name: item.name, position: index + 1 } }
else
data = []
ec_course_evaluation_subitems.each do |item|
evaluation_count.times do |i|
data << { id: item.id, name: "#{name}#{i + 1}#{item.name}", position: i + 1 }
end
end
data
end
end
end

@ -131,7 +131,7 @@ class Exercise < ApplicationRecord
status = 4
else
if user.present? && user.student_of_course?(course) #当为学生的时候,需根据分班来判断试卷状态
ex_time = get_exercise_times(user_id,false)
ex_time = get_exercise_times(user.id,false)
pb_time = ex_time[:publish_time]
ed_time = ex_time[:end_time]
if pb_time.present? && ed_time.present? && pb_time <= Time.now && ed_time > Time.now

@ -1,2 +1,3 @@
class Forum < ApplicationRecord
has_many :memos, dependent: :destroy
end

@ -136,7 +136,7 @@ class HomeworkCommon < ApplicationRecord
# 作业查看最新成绩
def update_score identity
identity < Course::NORMAL && publish_time.present? && publish_time < Time.now && !course.is_end
identity < Course::NORMAL && publish_time.present? && publish_time < Time.now && !end_or_late_none_group
end
# 作业能否立即发布

@ -5,7 +5,7 @@ class MirrorRepository < ApplicationRecord
scope :published_mirror, -> { where(status: 1) }
scope :published_mirror, -> { where(status: [1,2,3,5]) }
scope :published_main_mirror, -> { published_mirror.where(main_type: 1) }
scope :published_small_mirror, -> { published_mirror.where(main_type: 0) }

@ -1,2 +1,5 @@
class PortalImage < ApplicationRecord
def online?
status?
end
end

@ -2,4 +2,13 @@ class ShixunInfo < ApplicationRecord
belongs_to :shixun
validates_uniqueness_of :shixun_id
validates_presence_of :shixun_id
after_commit :create_diff_record
private
def create_diff_record
return unless description_previously_changed?
CreateDiffRecordJob.perform_later(User.current.id, id, 'ShixunInfo', 'description', description_before_last_save, description)
end
end

@ -20,6 +20,8 @@ class User < ApplicationRecord
VALID_EMAIL_REGEX = /^[a-zA-Z0-9]+([.\-_\\]*[a-zA-Z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/i
VALID_PHONE_REGEX = /^1\d{10}$/
# 身份证
VALID_NUMBER_REGEX = /^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
LOGIN_LENGTH_LIMIT = 30
MAIL_LENGTH_LMIT = 60
@ -162,7 +164,8 @@ class User < ApplicationRecord
# validates_format_of :mail, with: VALID_EMAIL_REGEX, multiline: true
# validates_format_of :phone, with: VALID_PHONE_REGEX, multiline: true
validate :validate_password_length
#validate :validate_ID_number
#validates_format_of :ID_number, with: VALID_NUMBER_REGEX, multiline: true, message: "身份证号格式不对"
# validates :nickname, presence: true, length: { maximum: 10 }
# validates :lastname, presence: true

@ -0,0 +1,35 @@
class Admins::DragPortalImageService < 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 # 未移动
images = PortalImage.all
ActiveRecord::Base.transaction do
if after.blank? || move.id == after.id # 移动至末尾
total = images.count
images.where('position > ?', move.position).update_all('position = position - 1')
move.update!(position: total)
return
end
if move.position > after.position # 前移
images.where('position >= ? AND position < ?', after.position, move.position).update_all('position = position + 1')
move.update!(position: after.position)
else # 后移
images.where('position > ? AND position < ?', move.position, after.position).update_all('position = position - 1')
move.update!(position: after.position - 1)
end
end
end
end

@ -0,0 +1,46 @@
class CreateDiffRecordService < ApplicationService
attr_reader :user, :obj, :column_name, :after, :before
def initialize(user, obj, column_name, before, after)
@user = user
@obj = obj
@before = before
@after = after
@column_name = column_name
end
def call
ActiveRecord::Base.transaction do
diff_record = DiffRecord.create!(user: user, container: obj, column_name: column_name)
diff_record.create_diff_record_content!(content: diff_content)
end
end
private
def diff_content
content = ''
arr = []
index = 0
fragment_size = 1
Diffy::Diff.new(before, after).each do |line|
unless line =~ /^[\+-]/
if arr.empty? && index < fragment_size
content += line
index += 1
else
index = 0
arr << line
arr.shift if arr.size > fragment_size
end
next
end
content += arr.join('') if arr.present?
content += line
arr.clear
end
content
end
end

@ -31,8 +31,8 @@ class Ecs::CreateCourseAchievementMethodsService < ApplicationService
# 创建新的评价方法时,全部为新建
if item[:id].blank? || course_target.ec_course_achievement_methods.find_by(id: item[:id]).blank?
item[:ec_achievement_evaluation_relates_attributes] =
Array(*item[:course_evaluation_subitem_ids]).uniq.map do |subitem_id|
{ ec_course_evaluation_subitem_id: subitem_id.to_i }
item[:course_evaluation_relates].map do |relate|
{ ec_course_evaluation_subitem_id: relate[:subitem_id].to_i, position: relate[:position] }
end
return
end
@ -41,22 +41,22 @@ class Ecs::CreateCourseAchievementMethodsService < ApplicationService
relates = achievement_method.ec_achievement_evaluation_relates
# 获取传入的 subitem id数组和已存在的 subitem id数组
old_subitem_ids = relates.map(&:ec_course_evaluation_subitem_id).uniq
new_subitem_ids = Array(*item[:course_evaluation_subitem_ids]).map(&:to_i).uniq
old_data = relates.map { |e| [e.ec_course_evaluation_subitem_id, e.position] }
new_data = item[:course_evaluation_relates].map { |e| [e[:subitem_id, e[:position]]] }
# 分别得到需要移除的 subitem ID数组和需要创建的 subitem ID数组
destroy_subitem_ids = old_subitem_ids - new_subitem_ids
create_subitem_ids = new_subitem_ids - old_subitem_ids
destroy_data = old_data - new_data
create_data = new_data - old_data
# 生成需要创建关系的 subitem id 数据
create_attributes = create_subitem_ids.map { |item_id| { ec_course_evaluation_subitem_id: item_id } }
create_attributes = create_data.map { |arr| { ec_course_target_id: course_target.id, ec_course_evaluation_subitem_id: arr[0], position: arr[1] } }
# 处理需要更新或者删除的记录
exists_attributes = relates.map do |relate|
if destroy_subitem_ids.include?(relate.ec_course_evaluation_subitem_id)
if destroy_data.include?([relate.ec_course_evaluation_subitem_id, relate.position])
{ id: relate.id, _destroy: true }
else
relate.as_json(only: %i[id ec_course_evaluation_subitem_id ec_course_achievement_method_id])
relate.as_json(only: %i[id ec_course_target_id ec_course_evaluation_subitem_id ec_course_achievement_method_id position])
end
end

@ -3,27 +3,29 @@ class Ecs::CreateCourseManagerService < ApplicationService
COURSE_MANAGER_COUNT_LIMIT = 2 # 课程管理员数量限制
attr_reader :ec_course, :user_id
attr_reader :ec_course, :user_ids
def initialize(ec_course, user_id)
def initialize(ec_course, user_ids)
@ec_course = ec_course
@user_id = user_id
@user_ids = user_ids
end
def call
user = User.find_by(id: params[:user_id])
raise Error, '该用户不存在' if user.blank?
users_count = User.where(id: user_ids).count
raise Error, '用户不存在' if users_count != user_ids.size
if ec_course.ec_course_users.exists?(user_id: user.id)
raise Error, '用户已经是该课程的管理员'
if ec_course.ec_course_users.exists?(user_id: user_ids)
raise Error, '用户已经是该课程的管理员'
end
if ec_course.ec_course_users.count >= COURSE_MANAGER_COUNT_LIMIT
raise Error, '该课程管理员数量已达上限'
if ec_course.ec_course_users.count + user_ids.size > COURSE_MANAGER_COUNT_LIMIT
raise Error, "课程管理员数量过多(最多#{COURSE_MANAGER_COUNT_LIMIT}"
end
ec_course.ec_course_users.create!(user: user)
user
ActiveRecord::Base.transaction do
user_ids.each do |user_id|
ec_course.ec_course_users.create!(user_id: user_id)
end
end
end
end

@ -4,6 +4,7 @@ class Ecs::QueryCourseEvaluationService < ApplicationService
def initialize(ec_course)
@ec_course = ec_course
@_course_achievement = 0
@_course_rate = 0
end
def course_targets
@ -16,6 +17,12 @@ class Ecs::QueryCourseEvaluationService < ApplicationService
@_course_achievement.round(2)
end
def course_rate
course_targets
@_course_rate.round(2)
end
def graduation_subitem_evaluations
student_scores = ec_course.ec_course_student_scores.joins(ec_student_score_targets: :ec_course_target).group(:ec_course_target_id)
student_scores = student_scores.select('AVG(score) as average_score, ec_course_target_id')
@ -58,10 +65,10 @@ class Ecs::QueryCourseEvaluationService < ApplicationService
end
end
def score_levels_map
@_score_levels_map ||= begin
def score_levels
@_score_levels ||= begin
index = 0
ec_course.ec_score_levels.each_with_object({}) do |level, obj|
ec_course.ec_score_levels.map do |level|
hash = level.as_json(only: %i[id position score level])
hash[:description] =
@ -72,7 +79,7 @@ class Ecs::QueryCourseEvaluationService < ApplicationService
end
index += 1
obj[level.id.to_s] = hash
hash
end
end
end
@ -96,6 +103,7 @@ class Ecs::QueryCourseEvaluationService < ApplicationService
# 计算总评成绩
@_course_achievement += data[:average_score].to_f * course_target.weight.to_f
@_course_rate += course_target.weight.to_f
# 计算学生成绩分布区间
student_count = 0

@ -25,10 +25,10 @@ class Users::ApplyAuthenticationService < ApplicationService
user.apply_user_authentication.create!(auth_type: 1, status: 0)
move_image_file! unless params[:upload_image].to_s == 'false'
sms_notify_admin
end
sms_notify_admin
user
end

@ -37,13 +37,9 @@ class Users::ApplyProfessionalAuthService < ApplicationService
user.apply_user_authentication.create!(auth_type: 2, status: 0)
move_image_file! unless params[:upload_image].to_s == 'false'
sms_cache = Rails.cache.read("apply_pro_certification")
if sms_cache.nil?
sms_notify_admin
Rails.cache.write("apply_pro_certification", 1, expires_in: 5.minutes)
end
end
sms_notify_admin
end
private
@ -61,7 +57,11 @@ class Users::ApplyProfessionalAuthService < ApplicationService
end
def sms_notify_admin
Educoder::Sms.notify_admin(send_type: 'apply_pro_certification')
sms_cache = Rails.cache.read('apply_pro_certification')
if sms_cache.nil?
Educoder::Sms.notify_admin(send_type: 'apply_pro_certification')
Rails.cache.write('apply_pro_certification', 1, expires_in: 5.minutes)
end
rescue => ex
Util.logger_error(ex)
end

@ -0,0 +1,43 @@
<%
define_admin_breadcrumbs do
add_admin_breadcrumb('轮播图')
end
%>
<div class="card mb-5 carousels-card">
<div class="card-header d-flex justify-content-between align-items-center">
<span class="flex-1">首页轮播图<span class="text-secondary font-12">(拖动排序)</span></span>
<%= javascript_void_link '添加', class: 'btn btn-primary btn-sm add-btn', data: { toggle: 'modal', target: '.admin-add-carousel-modal' } %>
</div>
<div class="card-body row" id="carousels-container">
<% @images.each_with_index do |image, index| %>
<div class="col-12 custom-carousel-item custom-carousel-item-<%= image.id %>" data-id="<%= image.id %>">
<div class="border rounded relative p-3 mb-3 drag row align-items-center <%= image.online? ? '' : 'not_active' %>">
<div class="col-2 col-md-1 custom-carousel-item-no"><%= index + 1 %></div>
<div class="col-10 col-md-3 custom-carousel-item-img" data-source-id="<%= image.id %>" data-source-type="PortalImage" data-toggle="modal" data-target=".admin-upload-file-modal">
<img src="<%= Util::FileManage.exist?('PortalImage', image.id) ? Util::FileManage.disk_file_url('PortalImage', image.id) : '' %>" data-toggle="tooltip" data-title="重新上传"/>
</div>
<div class="col-10 col-md-7">
<div class="input-group">
<input type="text" value="<%= image.name %>" class="form-control name-input" placeholder="请输入名称" />
<input type="text" value="<%= image.link %>" class="form-control link-input" placeholder="请输入跳转地址">
<div class="input-group-prepend">
<button class="input-group-text save-data-btn" data-id="<%= image.id %>">保存</button>
</div>
</div>
</div>
<div class="col-2 col-md-1 operate-box">
<%= check_box_tag(:online, 1, image.online?, id: nil, class: 'online-check-box', data: { id: image.id, toggle: 'tooltip', title: '首页展示' }) %>
<%= delete_link '删除', admins_carousel_path(image, element: ".custom-carousel-item-#{image.id}", not_refresh: true), class: 'delete-btn' do %>
<i class="fa fa-trash-o" data-id="<%= image.id %>"></i>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
<%= render partial: 'admins/carousels/shared/add_carousel_modal' %>
<%= render partial: 'admins/shared/modal/upload_file_modal' %>

@ -0,0 +1,36 @@
<div class="modal fade admin-add-carousel-modal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">添加轮播图</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<%= simple_form_for(PortalImage.new, url: admins_carousels_path, html: { class: 'admin-add-carousel-form', enctype: 'multipart/form-data' }) do |f| %>
<div class="form-group">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">图片</span>
</div>
<div class="custom-file flex-row-reverse">
<input type="file" name="portal_image[image]" class="img-file-input" id="img-file-input" accept="image/*">
<label class="custom-file-label file-names" for="img-file-input">选择文件</label>
</div>
</div>
</div>
<%= f.input :name, label: '名称', placeholder: '请输入名称' %>
<%= f.input :link, as: :url, label: '跳转地址', placeholder: '请输入跳转地址' %>
<div class="error text-danger"></div>
<% end %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary submit-btn">确认</button>
</div>
</div>
</div>
</div>

@ -7,7 +7,7 @@
<th width="14%" class="text-left">单位名称</th>
<th width="6%">地区</th>
<th width="6%">城市</th>
<th width="16%">详细地址</th>
<th width="16%" class="text-left">详细地址</th>
<th width="6%"><%= sort_tag('用户数', name: 'users_count', path: admins_schools_path) %></th>
<th width="6%">部门数</th>
<th width="12%"><%= sort_tag('创建时间', name: 'created_at', path: admins_schools_path) %></th>

@ -77,7 +77,8 @@
</li>
<li>
<%= sidebar_item_group('#helps-submenu', '帮助中心', icon: 'info-circle') do %>
<%= sidebar_item_group('#setting-submenu', '网站建设', icon: 'cogs') do %>
<li><%= sidebar_item(admins_carousels_path, '轮播图', icon: 'image', controller: 'admins-carousels') %></li>
<li><%= sidebar_item(edit_admins_about_path, '关于我们', icon: 'smile-o', controller: 'admins-abouts') %></li>
<li><%= sidebar_item(edit_admins_contact_us_path, '联系我们', icon: 'commenting-o', controller: 'admins-contact_us') %></li>
<li><%= sidebar_item(admins_cooperatives_path, '合作伙伴', icon: 'handshake-o', controller: 'admins-cooperatives') %></li>

@ -1,12 +1,7 @@
<% if @teachers.present? %>
<% @teachers.each_with_index do |teacher, index| %>
<tr>
<td class="pl20 pr20">
<% if index < 3 %>
<img src="/images/educoder/competition/<%= index + 1 %>.png" width="18px" height="22px" class="mt8"/></td>
<% else %>
<%= index + 1 %>
<% end %>
<td class="pl20 pr20"><%= index + 1 %></td>
<td class="color-dark"><a href="<%= user_path(teacher['login']) %>" target="_blank" class="task-hide" style="max-width: 84px;"><%= teacher['real_name'] %></a></td>
<td><%= teacher['course_count'] %></td>
<td><%= teacher['shixun_work_count'] %></td>

@ -24,5 +24,4 @@ json.course_identity @user_course_identity
json.excellent @course.excellent
if @course.is_end == 0
json.days_remaining (@course.end_date.to_date - Time.now.to_date).to_i
end
end

@ -1,3 +1,32 @@
json.course_targets @course_targets,
partial: 'ecs/course_targets/shared/ec_course_target_with_achievement_methods',
as: :ec_course_target
json.course_targets do
json.array! @course_targets do |course_target|
json.extract! course_target, :id, :position, :content
json.course_achievement_methods do
json.array! course_target.ec_course_achievement_methods do |achievement_method|
evaluation = achievement_method.ec_course_evaluation
json.extract! achievement_method, :id, :score, :percentage
json.course_evaluation do
json.partial! 'ecs/course_evaluations/shared/ec_course_evaluation_only', ec_course_evaluation: evaluation
end
json.course_evaluation_relates do
json.array! achievement_method.ec_achievement_evaluation_relates do |relate|
json.extract! relate, :id, :position, :ec_course_evaluation_subitem_id
subitem = relate.ec_course_evaluation_subitem
if evaluation.is_course_type?
json.name subitem&.name
else
json.name subitem&.name ? "#{evaluation.name}#{relate.position}:#{subitem&.name}" : "#{evaluation.name}#{relate.position}"
end
end
end
end
end
end
end

@ -1 +1,7 @@
json.course_evaluations @course_evaluations, partial: 'ecs/course_evaluations/shared/ec_course_evaluation_slim', as: :ec_course_evaluation
json.course_evaluations do
json.array! @course_evaluations do |course_evaluation|
json.partial! 'ecs/course_evaluations/shared/ec_course_evaluation_slim', ec_course_evaluation: course_evaluation
json.evaluation_relates course_evaluation.evaluation_relates
end
end

@ -1 +0,0 @@
json.partial! 'ecs/shared/user', user: @user

@ -1,2 +1,29 @@
json.course_targets do
json.array! @course_targets do |course_target|
json.extract! course_target, :id, :position, :content
json.course_targets @course_targets, partial: 'ecs/course_targets/shared/ec_course_target_with_achievement_methods', as: :ec_course_target
json.course_achievement_methods do
json.array! course_target.ec_course_achievement_methods do |achievement_method|
evaluation = achievement_method.ec_course_evaluation
json.extract! achievement_method, :id, :score, :percentage
json.course_evaluation do
json.partial! 'ecs/course_evaluations/shared/ec_course_evaluation_only', ec_course_evaluation: evaluation
end
json.course_evaluation_relates do
json.array! achievement_method.ec_achievement_evaluation_relates do |relate|
json.extract! relate, :id, :position, :ec_course_evaluation_subitem_id
subitem = relate.ec_course_evaluation_subitem
if evaluation.is_course_type?
json.name subitem&.name
else
json.name subitem&.name ? "#{evaluation.name}#{relate.position}:#{subitem&.name}" : "#{evaluation.name}#{relate.position}"
end
end
end
end
end
end
end

@ -2,4 +2,5 @@ json.partial! "graduation_topics/show_navigation", locals: {course: course, grad
json.task_status task_curr_status(graduation, course)[:status]
json.task_name graduation.name
json.task_id graduation.id
json.status graduation.status
json.status graduation.status
json.end_time graduation.end_time

@ -1,3 +1,4 @@
json.status 1
json.message "发送成功"
json.course_id @course.id
json.course_id @course.id
json.first_category_url module_url(@course.none_hidden_course_modules.first, @course)

@ -0,0 +1,3 @@
json.template do
json.partial! 'attachments/attachment_simple', attachment: @template.attachments.last
end

@ -12,5 +12,5 @@ rescue => ex
wechat_config = {}
end
Util::Wechat.appid = wechat_config['appid']
Util::Wechat.secret = wechat_config['secret']
Wechat::OfficialAccount.appid = wechat_config['appid']
Wechat::OfficialAccount.secret = wechat_config['secret']

@ -818,6 +818,7 @@ Rails.application.routes.draw do
post :feedback
end
end
resource :template, only: [:show]
end
namespace :admins do
@ -962,6 +963,9 @@ Rails.application.routes.draw do
post :drag, on: :collection
post :replace_image_url, on: :member
end
resources :carousels, only: [:index, :create, :update, :destroy] do
post :drag, on: :collection
end
end
resources :colleges, only: [] do

@ -6,15 +6,26 @@ class ChangeUserP02389416Exercise < ActiveRecord::Migration[5.2]
# 分数分别为2分2分5分2分2分2分2分2分 合计19分
question_ids = [37411,37414,37417,37418,37419,37423,37424,37429]
choice_ids = [117788,117797,117806,117809,117811,117816,117818,117828]
question_scores = [2,2,5,2,2,2,2,2]
question_ids.each_with_index do |q, index|
ExerciseAnswer.create(user_id: 45442, exercise_question_id: q, exercise_choice_id: choice_ids[index])
ex_exercise_user = ExerciseAnswer.where(user_id: 45442, exercise_question_id: q, exercise_choice_id: choice_ids[index])
if ex_exercise_user.exists?
ex_exercise_user.first.update_attribute(:score,question_scores[index])
else
ExerciseAnswer.create(user_id: 45442, exercise_question_id: q, exercise_choice_id: choice_ids[index], score: question_scores[index])
end
end
ex_user = ExerciseUser.where(user_id: 45442, exercise_id: 2561)&.first
if ex_user.present?
obj_score = ex_user.objective_score.to_i + 19
total_score = ex_user.score.to_i + 19
if ex_user.score > 65 || ex_user.objective_score > 65
ex_user.update_attributes(score: 65, objective_score: 65)
else
obj_score = ex_user.objective_score.to_i + 19
total_score = ex_user.score.to_i + 19
ex_user.update_attributes(score: total_score, objective_score: obj_score)
end
ex_user.update_attributes(score: total_score, objective_score: obj_score)
end
puts "====> end_to_create user exercise_answer"

@ -0,0 +1,9 @@
class MigrateForumName < ActiveRecord::Migration[5.2]
def change
forum = Forum.find_by(id: 16)
if forum.present?
forum.update_attributes(name: "通知公告")
forum.memos.destroy_all
end
end
end

@ -0,0 +1,11 @@
class CreateDiffRecords < ActiveRecord::Migration[5.2]
def change
create_table :diff_records do |t|
t.references :user
t.references :container, polymorphic: true
t.string :column_name
t.timestamps
end
end
end

@ -0,0 +1,9 @@
class CreateDiffRecordContents < ActiveRecord::Migration[5.2]
def change
create_table :diff_record_contents do |t|
t.references :diff_record
t.text :content
end
end
end

@ -0,0 +1,7 @@
class ResortPortalImageData < ActiveRecord::Migration[5.2]
def change
PortalImage.order(position: :asc).each_with_index do |image, index|
image.update!(position: index + 1)
end
end
end

File diff suppressed because one or more lines are too long

@ -24946,6 +24946,78 @@ input.form-control {
margin-right: 5px;
}
/* line 4, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
/* line 10, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-no {
font-size: 28px;
text-align: center;
}
/* line 15, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-img {
cursor: pointer;
width: 100%;
height: 60px;
}
/* line 20, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-img > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
/* line 28, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .not_active {
background: #F0F0F0;
}
/* line 32, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
/* line 38, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .save-url-btn {
cursor: pointer;
}
/* line 42, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .operate-box {
display: -webkit-box;
display: flex;
-webkit-box-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
align-items: center;
}
/* line 48, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .online-check-box {
font-size: 20px;
}
/* line 52, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .name-input {
-webkit-box-flex: 1;
flex: 1;
}
/* line 55, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .link-input {
-webkit-box-flex: 3;
flex: 3;
}
/* line 1, app/assets/stylesheets/admins/common.scss */
.admin-body-container {
padding: 20px;

@ -133846,6 +133846,132 @@ $(document).on('turbolinks:load', function(){
$('.batch-all-check-box').prop('checked', allChecked);
})
});
$(document).on('turbolinks:load', function() {
if ($('body.admins-carousels-index-page').length > 0) {
// ------------ 保存链接 -----------
$('.carousels-card').on('click', '.save-data-btn', function(){
var $link = $(this);
var id = $link.data('id');
var link = $('.custom-carousel-item-' + id).find('.link-input').val();
var name = $('.custom-carousel-item-' + id).find('.name-input').val();
if(!name || name.length == 0){
$.notify({ message: '名称不能为空' },{ type: 'danger' });
return;
}
$link.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { link: link, name: name },
success: function(data){
$.notify({ message: '操作成功' });
},
error: ajaxErrorNotifyHandler,
complete: function(){
$link.removeAttr('disabled');
}
})
});
// -------------- 是否在首页展示 --------------
$('.carousels-card').on('change', '.online-check-box', function(){
var $checkbox = $(this);
var id = $checkbox.data('id');
var checked = $checkbox.is(':checked');
$checkbox.attr('disabled', true);
$.ajax({
url: '/admins/carousels/' + id,
method: 'PATCH',
dataType: 'json',
data: { status: checked },
success: function(data){
$.notify({ message: '保存成功' });
var box = $('.custom-carousel-item-' + id).find('.drag');
if(checked){
box.removeClass('not_active');
}else{
box.addClass('not_active');
}
},
error: ajaxErrorNotifyHandler,
complete: function(){
$checkbox.removeAttr('disabled');
}
})
});
// ------------ 拖拽 -------------
var onDropFunc = function(el, _target, _source, sibling){
var moveId = $(el).data('id');
var insertId = $(sibling).data('id') || '';
$.ajax({
url: '/admins/carousels/drag',
method: 'POST',
dataType: 'json',
data: { move_id: moveId, after_id: insertId },
success: function(data){
$('#carousels-container .custom-carousel-item-no').each(function(index, ele){
$(ele).html(index + 1);
})
},
error: function(res){
var data = res.responseJSON;
$.notify({message: '移动失败,原因:' + data.message}, {type: 'danger'});
}
})
};
var ele1 = document.getElementById('carousels-container');
dragula([ele1], { mirrorContainer: ele1 }).on('drop', onDropFunc);
// ----------- 新增 --------------
var $createModal = $('.modal.admin-add-carousel-modal');
var $createForm = $createModal.find('form.admin-add-carousel-form');
$createForm.validate({
errorElement: 'span',
errorClass: 'danger text-danger',
rules: {
"portal_image[image]": {
required: true
},
"portal_image[name]": {
required: true
},
}
});
$createModal.on('show.bs.modal', function(event){
resetFileInputFunc($createModal.find('.img-file-input'));
$createModal.find('.file-names').html('选择文件');
});
$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 $carouselItem = $('.custom-carousel-item-' + data.source_id);
$carouselItem.find('.custom-carousel-item-img img').attr('src', data.url);
})
}
})
;
$(document).on('turbolinks:load', function() {
var $refuseModal = $('.admin-common-refuse-modal');
if ($refuseModal.length > 0) {

@ -24946,6 +24946,78 @@ input.form-control {
margin-right: 5px;
}
/* line 4, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
/* line 10, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-no {
font-size: 28px;
text-align: center;
}
/* line 15, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-img {
cursor: pointer;
width: 100%;
height: 60px;
}
/* line 20, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-img > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
/* line 28, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .not_active {
background: #F0F0F0;
}
/* line 32, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
/* line 38, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .save-url-btn {
cursor: pointer;
}
/* line 42, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .operate-box {
display: -webkit-box;
display: flex;
-webkit-box-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
align-items: center;
}
/* line 48, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .online-check-box {
font-size: 20px;
}
/* line 52, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .name-input {
-webkit-box-flex: 1;
flex: 1;
}
/* line 55, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .link-input {
-webkit-box-flex: 3;
flex: 3;
}
/* line 1, app/assets/stylesheets/admins/common.scss */
.admin-body-container {
padding: 20px;
@ -25853,6 +25925,77 @@ input.form-control {
font-size: 20px;
margin-right: 5px;
}
/* line 4, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item > .drag {
cursor: move;
background: #fff;
box-shadow: 1px 2px 5px 3px #f0f0f0;
}
/* line 10, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-no {
font-size: 28px;
text-align: center;
}
/* line 15, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-img {
cursor: pointer;
width: 100%;
height: 60px;
}
/* line 20, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item-img > img {
display: block;
width: 100%;
height: 60px;
background: #F5F5F5;
}
/* line 28, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .not_active {
background: #F0F0F0;
}
/* line 32, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .delete-btn {
font-size: 20px;
color: red;
cursor: pointer;
}
/* line 38, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .save-url-btn {
cursor: pointer;
}
/* line 42, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .operate-box {
display: -webkit-box;
display: flex;
-webkit-box-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
align-items: center;
}
/* line 48, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .online-check-box {
font-size: 20px;
}
/* line 52, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .name-input {
-webkit-box-flex: 1;
flex: 1;
}
/* line 55, app/assets/stylesheets/admins/carousels.scss */
.admins-carousels-index-page .carousels-card .custom-carousel-item .link-input {
-webkit-box-flex: 3;
flex: 3;
}
@charset "UTF-8";
/* line 1, app/assets/stylesheets/admins/common.scss */
.admin-body-container {

@ -79,7 +79,7 @@
"react-url-query": "^1.4.0",
"redux": "^4.0.0",
"redux-thunk": "2.3.0",
"showdown": "^1.8.6",
"rsuite": "^4.0.1",
"store": "^2.0.12",
"style-loader": "0.19.0",
"styled-components": "^4.1.3",

@ -22,14 +22,14 @@ let hashTimeout
// TODO 开发期多个身份切换
let debugType =""
if (isDev) {
const _search = window.location.search;
let parsed = {};
if (_search) {
parsed = queryString.parse(_search);
}
debugType = window.location.search.indexOf('debug=t') != -1 ? 'teacher' :
window.location.search.indexOf('debug=s') != -1 ? 'student' :
window.location.search.indexOf('debug=a') != -1 ? 'admin' : parsed.debug || 'admin'
const _search = window.location.search;
let parsed = {};
if (_search) {
parsed = queryString.parse(_search);
}
debugType = window.location.search.indexOf('debug=t') != -1 ? 'teacher' :
window.location.search.indexOf('debug=s') != -1 ? 'student' :
window.location.search.indexOf('debug=a') != -1 ? 'admin' : parsed.debug || 'admin'
}
window._debugType = debugType;
export function initAxiosInterceptors(props) {
@ -212,12 +212,12 @@ function initOnlineOfflineListener() {
$(window).bind("online", () => {
notification.destroy()
notification.success({
duration: null,
duration: 2,
message: '网络恢复正常',
description:
'网络恢复正常,感谢使用。',
})
});
});
$(window).bind("offline", () => {
notification.destroy()

@ -1,24 +1,25 @@
import React, { Component } from 'react';
import {Link} from 'react-router-dom'
const map={"blue":"colorblue","white":"colorwhite","grey":"colorgrey", 'orange': "color-orange"}
class WordsBtn extends Component {
constructor(props) {
super(props);
}
render() {
let{to, href}=this.props
return(
<React.Fragment>
{
to==undefined ?
<a href={href || "javascript:void(0)"} onClick={this.props.onClick} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</a>
:
<Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</Link>
}
</React.Fragment>
)
}
}
import React, { Component } from 'react';
import {Link} from 'react-router-dom'
const map={"blue":"colorblue","white":"colorwhite","grey":"colorgrey", 'orange': "color-orange"}
class WordsBtn extends Component {
constructor(props) {
super(props);
}
render() {
let{to, href,targets}=this.props
return(
<React.Fragment>
{
to==undefined&&targets==undefined ?
<a href={href || "javascript:void(0)"} onClick={this.props.onClick} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</a>:
targets!=undefined? <a href={to} target="_blank" className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</a>
:
<Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`}>{this.props.children}</Link>
}
</React.Fragment>
)
}
}
export default WordsBtn;

@ -661,6 +661,11 @@ class CoursesIndex extends Component{
(props) => (<ListPageIndex {...this.props} {...props} {...this.state} {...common}/>)
}
></Route>
<Route path="/courses/:coursesId/course_groups"
render={
(props) => (<ListPageIndex {...this.props} {...props} {...this.state} {...common}/>)
}
></Route>
{/* 普通作业 */}
<Route path="/courses/:coursesId/common_homeworks/:category_id" exact

@ -232,6 +232,11 @@ class ListPageIndex extends Component{
(props) => (<StudentsList {...this.props} {...props} {...this.state} />)
}
></Route>
<Route path="/courses/:coursesId/course_groups"
render={
(props) => (<StudentsList {...this.props} {...props} {...this.state} />)
}
></Route>
<Route path="/courses/:coursesId/exercises/:Id"
render={

@ -242,7 +242,7 @@ class Fileslistitem extends Component{
{
discussMessage.is_lock === true ?
<Tooltip title={ this.props.isNotMember===true?"私有属性,非课堂成员不能访问":"私有属性"} placement="bottom">
<Tooltip title={"私有属性,非课堂成员不能访问"} placement="bottom">
<i className="iconfont icon-guansuo color-grey-c ml10 font-16 fl mt4"></i>
</Tooltip>
:""
@ -329,7 +329,19 @@ class Fileslistitem extends Component{
<p className={this.props.isAdmin===true?"color-grey panel-lightgrey mt8 fl ml30":"color-grey panel-lightgrey mt8 fl ml13"} style={{width:'100%'}}>
<pre className="color-dark">资源描述 :{discussMessage.description===null?"暂无描述":discussMessage.description}</pre>
<style>
{
`
.isspans{
text-align: left;
white-space: pre-wrap;
word-break: break-all;
overflow-wrap: break-word;
}
`
}
</style>
<span className="color-dark isspans">资源描述 :{discussMessage.description===null?"暂无描述":discussMessage.description}</span>
{/*<span className="mr50">*/}
{/*/!*<span className="mr15 color-dark"></span>*!/*/}
{/*<span className="mr15 color-dark">*/}

@ -593,7 +593,7 @@ class Fileslists extends Component{
modalname:"立即发布",
visible:true,
typs:"start",
Topval:"学生将能立即查看和下载发布资源",
Topval:"学生将能立即收到资源",
// Botvalleft:"暂不发布",
// Botval:`本操作只对"未发布"的分班有效`,
// starttime:"发布时间:"+moment(moment(new Date())).format("YYYY-MM-DD HH:mm"),

@ -70,7 +70,7 @@ class BoardsListItem extends Component{
{ !!discussMessage.sticky && <span className="btn-cir btn-cir-red fl mt5 ml5">置顶</span> }
{
discussMessage.is_public == false ? (<Tooltip title={`${isAdminOrStudent ? '私有属性' : '私有属性,非课堂成员不能访问'}`} placement="bottom">
discussMessage.is_public == false ? (<Tooltip title={'私有属性,非课堂成员不能访问'} placement="bottom">
<i className="iconfont icon-guansuo color-grey-c ml10 font-16 fl mt4"></i>
</Tooltip>) : ""
}

@ -308,15 +308,11 @@ class CommonWorkDetailIndex extends Component{
onClick={() => this.setState({moduleName: '参考答案'})}
className={`${childModuleName == '参考答案' ? 'active' : '' } `}
to={`/courses/${courseId}/${moduleEngName}/${workId}/answer`}>参考答案</Link>}
{this.props.isAdmin() ?
<Link
onClick={() => this.setState({moduleName: '设置'})}
className={`${childModuleName == '设置' ? 'active' : '' } `}
style={{paddingLeft:'38px'}}
to={`/courses/${courseId}/${moduleEngName}/${workId}/setting`}>设置</Link>:
""
}
style={{paddingLeft:this.props.isAdmin()?'38px':'20px'}}
to={`/courses/${courseId}/${moduleEngName}/${workId}/setting`}>{this.props.isAdmin()?"设置":"得分规则"}</Link>
{/* { this.props.tabRightComponents } */}

@ -175,7 +175,7 @@ class CommonWorkItem extends Component{
{/* 只有非课堂成员且作业是私有的情况下才会为true */}
{
item.private_icon===true ?
(<Tooltip title={ isAdminOrStudent ? "私有属性" : "私有属性,非课堂成员不能访问"} placement="bottom" >
(<Tooltip title={"私有属性,非课堂成员不能访问"} placement="bottom" >
<i className="iconfont icon-guansuo color-grey-c ml10 font-16 fl"></i>
</Tooltip>) : ""
}

@ -1029,8 +1029,9 @@ class CommonWorkSetting extends Component{
<span className="font-16 fl color-dark">发布设置</span>
{
!startEditFlag && isAdmin ?
<a className="fr mr6" onClick={() => { this.setState({startEditFlag: true}) }}>
<Tooltip title="编辑"><i className="iconfont icon-bianjidaibeijing font-20 color-green"></i></Tooltip>
<a className="fr white-btn edu-blueline-btn mr10 mr6 lineh-24" onClick={() => { this.setState({startEditFlag: true}) }}>
编辑设置
{/*<Tooltip title="编辑"><i className="iconfont icon-bianjidaibeijing font-20 color-green"></i></Tooltip>*/}
</a>
:""
}
@ -1054,7 +1055,7 @@ class CommonWorkSetting extends Component{
{/* <Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":publish_time_type===true?"":""}>
</Tooltip> */}
<ConditionToolTip condition={moment(this.state.init_publish_time) < this.fetchMoment} title={"时间已过,不能再修改"}>
<ConditionToolTip condition={moment(this.state.init_publish_time) < this.fetchMoment} title={this.props.isAdmin()?"时间已过,不能再修改":""}>
<span>
<DatePicker
@ -1086,7 +1087,7 @@ class CommonWorkSetting extends Component{
<span>截止时间</span>
{/* <Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":end_time_type===true?"":""}>
</Tooltip> */}
<ConditionToolTip condition={moment(this.state.init_end_time) < this.fetchMoment} title={"时间已过,不能再修改"}>
<ConditionToolTip condition={moment(this.state.init_end_time) < this.fetchMoment} title={this.props.isAdmin()?"时间已过,不能再修改":""}>
<span>
<DatePicker
dropdownClassName="hideDisable"
@ -1209,8 +1210,8 @@ class CommonWorkSetting extends Component{
{/* 开启时间 */}
<div className={"h20 mb30 ml60"}>
<span>开启时间</span>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?"发布时间已过,则不能修改":""}>
<ConditionToolTip condition={moment(init_evaluation_start) < this.fetchMoment} title={"时间已过,不能再修改"}>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?this.props.isAdmin()?"发布时间已过,则不能修改":"":""}>
<ConditionToolTip condition={moment(init_evaluation_start) < this.fetchMoment} title={this.props.isAdmin()?"时间已过,不能再修改":""}>
<span>
<DatePicker
dropdownClassName="hideDisable"
@ -1243,7 +1244,7 @@ class CommonWorkSetting extends Component{
{/* <Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?"":""}>
</Tooltip> */}
<ConditionToolTip condition={moment(init_evaluation_end) < this.fetchMoment} title={"时间已过,不能再修改"}>
<ConditionToolTip condition={moment(init_evaluation_end) < this.fetchMoment} title={this.props.isAdmin()?"时间已过,不能再修改":""}>
<span>
<DatePicker
dropdownClassName="hideDisable"
@ -1282,7 +1283,7 @@ class CommonWorkSetting extends Component{
{/* 匿评数量 */}
<div className={"h20 mb30 ml60"}>
<span>匿评数量</span>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?"发布时间已过,则不能修改":""}>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?this.props.isAdmin()?"发布时间已过,则不能修改":"":""}>
<span>
<Input type="number" className="mr10" style={{width:"100px" }} value={evaluation_num} onInput={this.evaluation_num_change}
disabled={anonymous_comment && !noAuth? false : true} min={0} max={100}
@ -1295,7 +1296,7 @@ class CommonWorkSetting extends Component{
<div className={"h20 mb30 ml60"}>
<span>缺评扣分</span>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?"发布时间已过,则不能修改":""}>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?this.props.isAdmin()?"发布时间已过,则不能修改":"":""}>
<span>
<Input type="number" className="mr10" style={{width:"100px" }} value={absence_penalty} onInput={this.absence_penalty_change}
disabled={ anonymous_comment && !noAuth ? false : true} min={0} max={100}
@ -1342,7 +1343,7 @@ class CommonWorkSetting extends Component{
<span>结束时间</span>
{/* <Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?"":""}>
</Tooltip> */}
<ConditionToolTip condition={moment(init_appeal_time) < this.fetchMoment} title={"时间已过,不能再修改"}>
<ConditionToolTip condition={moment(init_appeal_time) < this.fetchMoment} title={this.props.isAdmin()?"时间已过,不能再修改":""}>
<span>
<DatePicker
dropdownClassName="hideDisable"
@ -1370,7 +1371,7 @@ class CommonWorkSetting extends Component{
{/* 违规匿评扣分: */}
<div className={"h20 mb30 ml60"}>
<span>违规匿评扣分</span>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?"发布时间已过,则不能修改":""}>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?this.props.isAdmin()?"发布时间已过,则不能修改":"":""}>
<span>
<Input type="number" className="mr10" style={{width:"100px" }} value={appeal_penalty} onInput={this.appeal_penalty_change}
disabled={ anonymous_appeal && !noAuth ? false : true} min={0} max={100}
@ -1392,7 +1393,7 @@ class CommonWorkSetting extends Component{
</div>
<div className={"mb30 ml60"}>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?"发布时间已过,则不能修改":""}>
<Tooltip placement="bottom" title={this.props.isSuperAdmin()?"":starttimetype===true?this.props.isAdmin()?"发布时间已过,则不能修改":"":""}>
<RadioGroup onChange={this.ta_mode_change} value={ta_mode}>
<Radio style={radioStyle} value={1} disabled={noAuth}>
普通模式<span className={"font-14 color-grey-9 ml10"}>选中则取各助教最终评分的平均分</span>

@ -1,184 +1,184 @@
import React, { Component } from 'react';
export const STATUS_UN_PUBLISH = "未发布"
// homework_status: ["提交中", "未开启补交"]
export const STATUS_SUBMIT = "提交中"
// export const STATUS_UN_PUBLISH = "未发布"
// homework_status: ["提交中"] 未发布 未开启补交
export function RouteHOC(options = {}) {
return function wrap(WrappedComponent) {
return class Wrapper extends Component {
constructor(props) {
super(props);
this.state = {
}
}
toCreateProject = () => {
let url = '/projects/new'
if (window.location.port == 3007) {
// window.location.href
url = '/testbdweb.educoder.net/projects/new'
}
window.open(
url,
'_blank' // <- This is what makes it open in a new window.
);
}
// common_homework group_homework
// 是否是分组作业
isGroup = () => {
return window.location.pathname.indexOf('group_homeworks') != -1
}
getModuleName = (isChinese) => {
const isGroup = this.isGroup()
if (isChinese) {
let chName = isGroup ? '分组作业' : '普通作业'
return chName;
}
const secondName = isGroup ? 'group_homeworks' : 'common_homeworks'
return secondName;
}
getModuleType = () => {
const isGroup = this.isGroup()
return isGroup ? 3 : 1
}
toDetailPage = (_courseId, workId, topicId) => {
if (typeof _courseId == "object") {
const topicId = _courseId.topicId
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/boards/${workId}/messages/${topicId}`)
} else {
this.props.history.push(`/courses/${_courseId}/boards/${workId}/messages/${topicId}`)
}
}
toEditPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/edit`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/edit`)
}
}
toWorkDetailPage = (_courseId, _workId, _studentWorkId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
const studentWorkId = _courseId.studentWorkId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/${_studentWorkId || studentWorkId}/appraise`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/${_studentWorkId}/appraise`)
}
}
toNewPage = (courseId) => {
const secondName = this.getModuleName()
this.props.history.push(`/courses/${courseId.coursesId}/${secondName}/${courseId.category_id}/new`)
}
toListPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}${_workId ? '/' + _workId : ''}`)
}
}
toWorkPostPage = (_courseId, _workId, isEdit, _studentWorkId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
const studentWorkId = _courseId.studentWorkId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/${isEdit? `${_studentWorkId || studentWorkId}/post_edit` : 'post'}`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/${isEdit? `${_studentWorkId}/post_edit` : 'post'}`)
}
}
toWorkListPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/list`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/list`)
}
}
toWorkAnswerPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${workId}/answer`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/answer`)
}
}
toWorkQuestionPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _workId || _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${workId}/question`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/question`)
}
}
toWorkSettingPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/setting`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/setting`)
}
}
render() {
const { snackbarOpen} = this.state;
return (
<React.Fragment>
<WrappedComponent {...this.props}
toDetailPage={this.toDetailPage}
toEditPage={this.toEditPage}
toNewPage={this.toNewPage}
toListPage={this.toListPage}
toWorkDetailPage={this.toWorkDetailPage}
toWorkPostPage={this.toWorkPostPage}
toWorkListPage={this.toWorkListPage}
toWorkAnswerPage={this.toWorkAnswerPage}
toWorkQuestionPage={this.toWorkQuestionPage}
toWorkSettingPage={this.toWorkSettingPage}
toCreateProject={this.toCreateProject}
isGroup={this.isGroup}
getModuleName={this.getModuleName}
getModuleType={this.getModuleType}
>
</WrappedComponent>
</React.Fragment>
)
}
}
}
import React, { Component } from 'react';
export const STATUS_UN_PUBLISH = "未发布"
// homework_status: ["提交中", "未开启补交"]
export const STATUS_SUBMIT = "提交中"
// export const STATUS_UN_PUBLISH = "未发布"
// homework_status: ["提交中"] 未发布 未开启补交
export function RouteHOC(options = {}) {
return function wrap(WrappedComponent) {
return class Wrapper extends Component {
constructor(props) {
super(props);
this.state = {
}
}
toCreateProject = () => {
let url = '/projects/new'
if (window.location.port == 3007) {
// window.location.href
url = '/testbdweb.educoder.net/projects/new'
}
window.open(
url,
'_blank' // <- This is what makes it open in a new window.
);
}
// common_homework group_homework
// 是否是分组作业
isGroup = () => {
return window.location.pathname.indexOf('group_homeworks') != -1
}
getModuleName = (isChinese) => {
const isGroup = this.isGroup()
if (isChinese) {
let chName = isGroup ? '分组作业' : '普通作业'
return chName;
}
const secondName = isGroup ? 'group_homeworks' : 'common_homeworks'
return secondName;
}
getModuleType = () => {
const isGroup = this.isGroup()
return isGroup ? 3 : 1
}
toDetailPage = (_courseId, workId, topicId) => {
if (typeof _courseId == "object") {
const topicId = _courseId.topicId
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/boards/${workId}/messages/${topicId}`)
} else {
this.props.history.push(`/courses/${_courseId}/boards/${workId}/messages/${topicId}`)
}
}
toEditPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/edit`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/edit`)
}
}
toWorkDetailPage = (_courseId, _workId, _studentWorkId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
const studentWorkId = _courseId.studentWorkId
window.open(`/courses/${courseId}/${secondName}/${_workId || workId}/${_studentWorkId || studentWorkId}/appraise`);
} else {
window.open(`/courses/${_courseId}/${secondName}/${_workId}/${_studentWorkId}/appraise`);
}
}
toNewPage = (courseId) => {
const secondName = this.getModuleName()
this.props.history.push(`/courses/${courseId.coursesId}/${secondName}/${courseId.category_id}/new`)
}
toListPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}${_workId ? '/' + _workId : ''}`)
}
}
toWorkPostPage = (_courseId, _workId, isEdit, _studentWorkId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
const studentWorkId = _courseId.studentWorkId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/${isEdit? `${_studentWorkId || studentWorkId}/post_edit` : 'post'}`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/${isEdit? `${_studentWorkId}/post_edit` : 'post'}`)
}
}
toWorkListPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/list`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/list`)
}
}
toWorkAnswerPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${workId}/answer`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/answer`)
}
}
toWorkQuestionPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _workId || _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${workId}/question`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/question`)
}
}
toWorkSettingPage = (_courseId, _workId) => {
const secondName = this.getModuleName()
if (typeof _courseId == "object") {
const workId = _courseId.workId
const courseId = _courseId.coursesId
this.props.history.push(`/courses/${courseId}/${secondName}/${_workId || workId}/setting`)
} else {
this.props.history.push(`/courses/${_courseId}/${secondName}/${_workId}/setting`)
}
}
render() {
const { snackbarOpen} = this.state;
return (
<React.Fragment>
<WrappedComponent {...this.props}
toDetailPage={this.toDetailPage}
toEditPage={this.toEditPage}
toNewPage={this.toNewPage}
toListPage={this.toListPage}
toWorkDetailPage={this.toWorkDetailPage}
toWorkPostPage={this.toWorkPostPage}
toWorkListPage={this.toWorkListPage}
toWorkAnswerPage={this.toWorkAnswerPage}
toWorkQuestionPage={this.toWorkQuestionPage}
toWorkSettingPage={this.toWorkSettingPage}
toCreateProject={this.toCreateProject}
isGroup={this.isGroup}
getModuleName={this.getModuleName}
getModuleType={this.getModuleType}
>
</WrappedComponent>
</React.Fragment>
)
}
}
}
}

@ -144,13 +144,11 @@ class WorkDetailPageHeader extends Component{
{view_answer == true && <Link
className={`${childModuleName == '参考答案' ? 'active' : '' } `}
to={`/courses/${courseId}/${moduleEngName}/${workId}/answer`}>参考答案</Link>}
{this.props.isAdmin()?
<Link
className={`${childModuleName == '设置' ? 'active' : '' } `}
style={{paddingLeft:'38px'}}
to={`/courses/${courseId}/${moduleEngName}/${workId}/setting`}>设置</Link>:
""
}
style={{paddingLeft:this.props.isAdmin()?'38px':'20px'}}
to={`/courses/${courseId}/${moduleEngName}/${workId}/setting`}>{this.props.isAdmin()?"设置":"得分规则"}</Link>
{ this.props.tabRightComponents }

@ -33,6 +33,7 @@ class commonWork extends Component{
modalsBottomval:"",
modalCancel:"",
mainList:undefined,
selectedKeys: 'all',
order:"",
page:1,
search:"",
@ -77,7 +78,9 @@ class commonWork extends Component{
componentDidUpdate(prevProps, prevState) {
if (prevProps.match.path != this.props.match.path) {
this.clearSelection()
this._getList()
this.setState({ selectedKeys: 'all', order: '' }, () => {
this._getList()
})
}
}
@ -143,6 +146,7 @@ class commonWork extends Component{
this.clearSelection()
this.setState({
order:e.key==="all"?"":e.key,
selectedKeys: e.key,
page:1,
isSpin:true,
checkBoxValues:[],
@ -403,7 +407,7 @@ class commonWork extends Component{
}
secondRowBotton={
<div className="fl mt6 task_menu_ul">
<Menu mode="horizontal" defaultSelectedKeys="all" onClick={this.selectedStatus}>
<Menu mode="horizontal" selectedKeys={this.state.selectedKeys} onClick={this.selectedStatus}>
<Menu.Item key="all">全部</Menu.Item>
{isAdmin && <Menu.Item key="0">未发布</Menu.Item>}
<Menu.Item key="1">提交中</Menu.Item>

@ -55,9 +55,13 @@ class CoursesBanner extends Component {
}
}
componentDidMount() {
this.onloadupdatabanner()
on('updatabanner', this.updatabanner)
if(this.props.match.path==="/courses/:coursesId"){
if(this.props.user!=undefined){
this.props.history.push(this.props.user.first_category_url)
}
}
axios.interceptors.response.use((response) => {
if (response != undefined)
if (response && response.data.status === 410) {
@ -69,9 +73,17 @@ class CoursesBanner extends Component {
}
return response;
}, (error) => {
});
}
componentDidUpdate(prevProps) {
if(prevProps.user!=this.props.user){
if(this.props.match.path==="/courses/:coursesId"){
if(this.props.user!=undefined){
this.props.history.push(this.props.user.first_category_url)
}
}
}
}
componentWillUnmount() {
off('updatabanner', this.updatabanner)
}

@ -57,6 +57,7 @@ class Coursesleftnav extends Component{
positiontype:undefined,
toopvisible:false,
toopvisibleindex:undefined,
toopvisibleindexs:undefined,
sandiantypes:undefined,
antIcon:false,
chapterupdate:false,
@ -314,7 +315,11 @@ class Coursesleftnav extends Component{
twosandianshow=(e,key,type)=>{
// console.log("twosandianshow");
// console.log(key);
// console.log(type);
this.setState({
toopvisibleindexs:key,
twosandiantype:key,
toopvisible:false,
toopvisibleindex:undefined,
@ -322,11 +327,29 @@ class Coursesleftnav extends Component{
})
e.stopPropagation();//阻止冒泡
}
twosandianshowys=(e,key,type)=>{
// console.log("twosandianshow");
// console.log(key);
// console.log(type);
this.setState({
toopvisibleindexs:key,
})
e.stopPropagation();//阻止冒泡
}
twosandianshowyss=(e,key,type)=>{
// console.log("twosandianshow");
// console.log(key);
// console.log(type);
this.setState({
toopvisibleindexs:undefined,
})
e.stopPropagation();//阻止冒泡
}
twosandianhide=(e,index,type)=>{
// console.log(index)
this.setState({
toopvisibleindexs:undefined,
twosandiantype:undefined,
twosandiantypenum:undefined,
toopvisible:true,
@ -336,6 +359,13 @@ class Coursesleftnav extends Component{
e.stopPropagation();//阻止冒泡
}
twosandianhideys=(e,index,type)=>{
// console.log(index)
this.setState({
toopvisibleindexs:undefined,
})
e.stopPropagation();//阻止冒泡
}
//置顶
editSetup=(e,id)=>{
@ -820,6 +850,7 @@ class Coursesleftnav extends Component{
ModalSave,
loadtype,
twosandiantypes,
toopvisibleindexs
}=this.state;
let {course_modules,hidden_modules,is_teacher} =this.props;
@ -992,6 +1023,8 @@ class Coursesleftnav extends Component{
}
}
}
// console.log(iem.category_name);
// console.log(iem.category_name.length);
return(
<Draggable
key={'id'+index}
@ -1001,33 +1034,53 @@ class Coursesleftnav extends Component{
>
{(provided, snapshot) => (
<Tooltip placement="bottom" title={"拖拽二级菜单调整顺序"}
key={index}
// visible={toopvisible===true&&toopvisibleindex===iem.category_id?true:false}
visible={false}
>
{/*"/courses/"+this.props.match.params.coursesId+"/"+item.type+"/"+iem.category_type+"/"+iem.category_id*/}
<a className={"Draggablelichild"}>
<li className="clearfix width93 Draggableli" key={index} onClick={(e)=>this.selectnavids(e,key,iem.category_id,item.type+"child",iem.second_category_url,key)} onMouseLeave={(e)=>this.twosandianhide(e,index,item.type)} onMouseEnter={(e)=>this.twosandianshow(e,index,item.type)}
<a className={"Draggablelichild"} key={index}>
<li className="clearfix width93 Draggableli" key={index} onClick={(e)=>this.selectnavids(e,key,iem.category_id,item.type+"child",iem.second_category_url,key)} onMouseLeave={(e)=>this.twosandianhide(e,index,item.type)} onMouseEnter={(e)=>this.twosandianshow(e,index,item.type)}
key={index}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
title={iem.category_name.length<10?"":iem.category_name}
// title={iem.category_name.length<10?"":iem.category_name}
>
<a className="fl pl46 pd0 Draggablelichild">
<span className={this.props.location.pathname===iem.second_category_url?"color-blue fl ml38 maxwidth155 task-hide Draggablelichild":"fl ml38 maxwidth155 task-hide Draggablelichild"}>{iem.category_name}</span>
<span className={this.props.location.pathname===iem.second_category_url?"color-blue fl ml38 maxwidth170 task-hide Draggablelichild":"fl ml38 maxwidth170 task-hide Draggablelichild"} onMouseEnter={(e)=>this.twosandianshowys(e,index,item.type)}>{iem.category_name}</span>
<span className={twosandiantype===undefined?this.props.location.pathname===iem.second_category_url?"fr mr20 color-blue Draggablelichild font-14":"fr mr20 color999 Draggablelichild font-14":item.type===twosandiantypes&&twosandiantype===index&&iem.category_id!=0?"none":this.props.location.pathname===iem.second_category_url?"fr mr20 color-blue Draggablelichild font-14":"fr mr20 color999 Draggablelichild font-14"} >{iem.category_count===0?"":iem.category_count}</span>
{item.type===twosandiantypes&&twosandiantype===index?
iem.category_id===0?"":
iem.category_type==="graduation_topics"||iem.category_type==="graduation_tasks"?
<span className={"fr mr20 color999 Draggablelichild font-14"} >{iem.category_count===0?"":iem.category_count}</span>
:<Popover placement="right" content={this.content(item,iem,index)} trigger="hover" key={index}>
(
iem.category_name&&iem.category_name.length<13?
<span className={"fr mr20 color999 Draggablelichild font-14"} >{iem.category_count===0?"":iem.category_count}</span>
:
<Tooltip placement="right" key={index} title={iem.category_name} visible={toopvisibleindexs===undefined?false:toopvisibleindexs===index?true:false}>
<span className={"fr mr20 color999 Draggablelichild font-14"} >{iem.category_count===0?"":iem.category_count}</span>
</Tooltip>
)
:
(
iem.category_name&&iem.category_name.length<13?
<Popover placement="right" content={this.content(item,iem,index)} trigger="hover" key={index} onMouseEnter={(e)=>this.twosandianshowyss(e)}>
<i className={"iconfont icon-sandian fr color999 mr15 Draggablelichild"}></i>
</Popover>
:
<Tooltip placement="right" key={index} title={iem.category_name} visible={toopvisibleindexs===undefined?false:toopvisibleindexs===index?true:false}>
<Popover placement="right" content={this.content(item,iem,index)} trigger="hover" key={index} onMouseEnter={(e)=>this.twosandianshowyss(e)}>
<i className={"iconfont icon-sandian fr color999 mr15 Draggablelichild"}></i>
</Popover>:""}
</Popover>
</Tooltip>
)
:""}
</a>
{provided.placeholder}
</li>
</a>
</Tooltip>
)}
@ -1087,17 +1140,35 @@ class Coursesleftnav extends Component{
}
}
}
// console.log(iem.category_name);
// console.log(iem.category_name.length);一开始是10 显示是13
return(
<a >
<li className="clearfix Draggableli" key={index} style={{ width: '244px'}} title={iem.category_name.length<10?"":iem.category_name}>
<a className="fl pl46 pd0 Draggablelichild" onClick={(e)=>this.selectnavids(e,key,iem.category_id,item.type+"child",iem.second_category_url,key)} >
{/*<span className="fl ml38 maxwidth155 task-hide">{iem.category_name}</span>*/}
<span className={this.props.location.pathname===iem.second_category_url?"color-blue fl ml38 maxwidth155 task-hide Draggablelichild":"fl ml38 maxwidth155 task-hide Draggablelichild"}>{iem.category_name}</span>
<span className={twosandiantype===undefined?this.props.location.pathname===iem.second_category_url?"fr mr20 color-blue font-14":"fr mr20 color999 font-14":twosandiantype===index&&item.type!="graduation"?"none":this.props.location.pathname===iem.second_category_url?"fr mr20 color-blue font-14":"fr mr20 color999 font-14"}>{iem.category_count===0?"":iem.category_count}</span>
</a>
{/*title={iem.category_name.length<10?"":iem.category_name}*/}
<li className="clearfix Draggableli" key={index} style={{ width: '244px'}} >
{
iem.category_name&&iem.category_name.length<13?
<a className="fl pl46 pd0 Draggablelichild" onClick={(e)=>this.selectnavids(e,key,iem.category_id,item.type+"child",iem.second_category_url,key)} >
{/*<span className="fl ml38 maxwidth170 task-hide">{iem.category_name}</span>*/}
{/*{iem.category_name.length<10?"":*/}
{/* iem.category_name}*/}
<span className={this.props.location.pathname===iem.second_category_url?"color-blue fl ml38 maxwidth170 task-hide Draggablelichild":"fl ml38 maxwidth170 task-hide Draggablelichild"}>{iem.category_name}</span>
<span className={twosandiantype===undefined?this.props.location.pathname===iem.second_category_url?"fr mr20 color-blue font-14":"fr mr20 color999 font-14":twosandiantype===index&&item.type!="graduation"?"none":this.props.location.pathname===iem.second_category_url?"fr mr20 color-blue font-14":"fr mr20 color999 font-14"}>{iem.category_count===0?"":iem.category_count}</span>
</a>
:
<Tooltip placement="right" key={index} title={iem.category_name}>
<a className="fl pl46 pd0 Draggablelichild" onClick={(e)=>this.selectnavids(e,key,iem.category_id,item.type+"child",iem.second_category_url,key)} >
{/*<span className="fl ml38 maxwidth170 task-hide">{iem.category_name}</span>*/}
{/*{iem.category_name.length<10?"":*/}
{/* iem.category_name}*/}
<span className={this.props.location.pathname===iem.second_category_url?"color-blue fl ml38 maxwidth170 task-hide Draggablelichild":"fl ml38 maxwidth170 task-hide Draggablelichild"}>{iem.category_name}</span>
<span className={twosandiantype===undefined?this.props.location.pathname===iem.second_category_url?"fr mr20 color-blue font-14":"fr mr20 color999 font-14":twosandiantype===index&&item.type!="graduation"?"none":this.props.location.pathname===iem.second_category_url?"fr mr20 color-blue font-14":"fr mr20 color999 font-14"}>{iem.category_count===0?"":iem.category_count}</span>
</a>
</Tooltip>
}
</li>
</a>
)
})

@ -153,7 +153,7 @@ class AppraiseModal extends Component{
</style>
<div className="clearfix">
<p className={"font mt10 mb10 ml10"}>
可见:(学生可查看老师的评阅内容
可见(学生可查看老师的评阅内容
</p>
{/*<Radio.Group onChange={this.onChanges} value={this.state.valuetype}>*/}
{/*<Radio value={0} style={radioStyle} className={"newfont"}>可见 (学生查看老师的评阅内容)</Radio>*/}
@ -167,7 +167,7 @@ class AppraiseModal extends Component{
/>
<p className={"font mt10 mb10 ml10"}>
不可见:(仅对课堂老师可见
不可见(仅对课堂老师可见
</p>
<WordNumberTextarea
placeholder={"请填写评阅内容"}

@ -94,7 +94,19 @@ class ModulationModal extends Component{
<li style={{height:"20px",lineHeight:"20px"}}><span className={textareavaltype===true?"color-red":"none"}>原因不能为空</span></li>
</div>
<li>
<style>
{
`
.pdl10{
padding-left:10px;
}
`
}
</style>
<li className={"pdl10"}>
<Input style={{
width: '20%',
}}

@ -101,6 +101,7 @@ class ShixunChooseModal extends Component{
return(
shixunmodal===true?<NewShixunModel
statustype={'published'}
type={'shixuns'}
datas={datas}
category_id={this.props.match.params.category_id}
NewShixunModelType={shixunmodal}

@ -427,6 +427,7 @@ a.white-btn.use_scope-btn:hover{
.ebebeb{border-bottom: 1px solid #EBEBEB;}
.CheckboxGroup{background:rgba(249,249,249,1);}
.maxwidth155{max-width: 155px; color:#666666;font-size: 14px;}
.maxwidth170{max-width: 170px; color:#666666;font-size: 14px;}
.pl46{ margin-left: 46px !important; border-bottom: 1px solid #eeee; width: 90% !important;}
.hidden{overflow: hidden;}
.pd0{padding: 0px !important;}

@ -371,7 +371,7 @@ class Elearning extends Component{
<Progress percent={learned} showInfo={false} />
</div>
<div style={{marginTop:"7px",textAlign: "left"}}>
<span className="font-16">上次学</span><span style={{color:"#4CADFF",marginLeft:"25px"}}>{last_shixun}</span>
<span className="font-16">上次学习内容</span><span style={{color:"#4CADFF",marginLeft:"25px"}}>{last_shixun}</span>
</div>

@ -94,10 +94,10 @@ class YslDetailCards extends Component{
startshixunCombattype:true,
})
} else {
console.log("开始学习了");
// console.log("开始学习了");
window.open("/tasks/" + response.data.game_identifier,'_blank');
//这个是传过来 调用刷新
this.props.Myreload();
this.props.getPathCardsList();
// window.location.href = path
// let path="/tasks/"+response.data.game_identifier;
// this.props.history.push(path);
@ -109,6 +109,7 @@ class YslDetailCards extends Component{
};
componentDidMount(){
// console.log("YslDetailCards start");
let pathid=this.props.match.params.coursesId;
this.setState({
pathid:pathid
@ -116,6 +117,11 @@ class YslDetailCards extends Component{
}
Pathlisteditundefined=()=>{
this.setState({
pathlistedit:undefined
})
};
hidestartshixunsreplace=(url)=>{
this.setState({
isSpin:true,
@ -521,6 +527,7 @@ class YslDetailCards extends Component{
ysldetailcards={"ysldetailcards"}
pathid={subject_id}
coursesId={pathid}
Pathlisteditundefined={this.Pathlisteditundefined}
></DetailCardsEditAndEdit>
:""
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save