dev_cxt
jingquan huang 5 years ago
commit 8fbd474e88

@ -5,9 +5,10 @@ class Admins::ShixunSettingsController < Admins::BaseController
shixun_settings = Admins::ShixunSettingsQuery.call(params)
@editing_shixuns = shixun_settings.where(status:0).size
@pending_shixuns = shixun_settings.where(status:1).size
@processed_shixuns = shixun_settings.where(status:2).size
@processed_shixuns = shixun_settings.where(status:2, public: 0).size
@closed_shixuns = shixun_settings.where(status:3).size
@pending_public_shixuns = shixun_settings.where(public:1).size
@processed_pubic_shixuns = shixun_settings.where(public:2).size
@sort_json = {
can_copy: params[:can_copy].present? ? params[:can_copy] : false,
@ -83,9 +84,9 @@ class Admins::ShixunSettingsController < Admins::BaseController
sheet1[count_row, 6] = shixun.myshixuns_count
sheet1[count_row, 7] = shixun.myshixuns.select{|m| m.status == 1}.size
sheet1[count_row, 8] = shixun.shixun_status
sheet1[count_row, 9] = shixun.user.show_real_name
sheet1[count_row, 10] = shixun.user.school_name
sheet1[count_row, 11] = shixun.user.identity
sheet1[count_row, 9] = shixun.user&.show_real_name
sheet1[count_row, 10] = shixun.user&.school_name
sheet1[count_row, 11] = shixun.user&.identity
shixun.challenges.each do |challenge|
sheet1[count_row, 12] = "#{challenge.position}"
sheet1[count_row, 13] = challenge.subject
@ -112,13 +113,16 @@ class Admins::ShixunSettingsController < Admins::BaseController
sheet1[count_row, 2] = shixun.mirror_repositories.select{|mr| mr.main_type == "1"}.first&.type_name
sheet1[count_row, 3] = shixun.fork_from
sheet1[count_row, 4] = shixun.shixun_status
sheet1[count_row, 5] = shixun.user.show_real_name
sheet1[count_row, 6] = shixun.user.school_name
sheet1[count_row, 7] = shixun.user.identity
shixun.challenges.each do |challenge|
sheet1[count_row, 5] = shixun.user&.show_real_name
sheet1[count_row, 6] = shixun.user&.school_name
sheet1[count_row, 7] = shixun.user&.identity
challenge_count = shixun.challenges.count
shixun.challenges.each_with_index do |challenge, index|
sheet1[count_row, 8] = "#{challenge.position}"
sheet1[count_row, 9] = challenge.subject
count_row += 1
if index + 1 != challenge_count
count_row += 1
end
end
count_row += 1
end

@ -5,10 +5,8 @@ class Admins::ShixunsController < Admins::BaseController
params[:sort_direction] = params[:sort_direction].presence || 'desc'
shixuns = Admins::ShixunQuery.call(params)
@editing_shixuns = shixuns.where(status:0).size
@pending_shixuns = shixuns.where(status:1).size
@processed_shixuns = shixuns.where(status:2).size
@processed_shixuns = shixuns.where(status:2, public: 0).size
@closed_shixuns = shixuns.where(status:3).size
@none_public_shixuns = shixuns.where(public:0).size
@pending_public_shixuns = shixuns.where(public:1).size
@processed_pubic_shixuns = shixuns.where(public:2).size
@shixuns_type_check = MirrorRepository.pluck(:type_name,:id)

@ -22,7 +22,7 @@ class CommentsController < ApplicationController
@discuss = @hack.discusses.new(reply_params)
@discuss.hidden = false
@discuss.user_id = current_user.id
@discuss.root_id = params[:parent_id]
@discuss.root_id = params[:comments][:parent_id]
@discuss.save!
rescue Exception => e
uid_logger_error("reply discuss failed : #{e.message}")
@ -32,9 +32,14 @@ class CommentsController < ApplicationController
# 列表
def index
disscusses = @hack.disscusses.where(:root_id => nil)
@disscuss_count = disscusses.count
@disscusses= paginate disscusses
discusses =
if current_user.admin_or_business?
@hack.discusses.where(root_id: nil)
else
@hack.discusses.where(root_id: nil, hidden: false)
end
@discusses_count = discusses.count
@discusses= paginate discusses
end
# 删除
@ -43,10 +48,21 @@ class CommentsController < ApplicationController
render_ok
end
# 隐藏、取消隐藏
def hidden
if current_user.admin_or_business?
@discuss = @hack.discusses.where(id: params[:id]).first
@discuss.update_attribute(:hidden, params[:hidden].to_i == 1)
sucess_status
else
Educoder::TipException(403, "..")
end
end
private
def find_hack
@hack = Hack.find_by_identifier params[:identifier]
@hack = Hack.find_by_identifier(params[:hack_identifier])
end
def comment_params

@ -117,13 +117,13 @@ class DiscussesController < ApplicationController
# 0 取消赞;
def plus
pt = PraiseTread.where(:praise_tread_object_id => params[:id], :praise_tread_object_type => params[:container_type],
:user_id => current_user, :praise_or_tread => 1).first
:user_id => current_user, :praise_or_tread => 1)
# 如果当前用户已赞过,则不能重复赞
if params[:type] == 1 && pt.blank?
PraiseTread.create!(:praise_tread_object_id => params[:id], :praise_tread_object_type => params[:container_type],
:user_id => current_user.id, :praise_or_tread => 1) if pt.blank?
:user_id => current_user.id, :praise_or_tread => 1)
else
pt.destroy if pt.present? # 如果已赞过,则删掉这条赞(取消);如果没赞过,则为非法请求不处理
pt.destroy_all if pt.present? # 如果已赞过,则删掉这条赞(取消);如果没赞过,则为非法请求不处理
end
@praise_count = PraiseTread.where(:praise_tread_object_id => params[:id], :praise_tread_object_type => params[:container_type],

@ -10,9 +10,6 @@ class GamesController < ApplicationController
include GamesHelper
include ApplicationHelper
def show
uid_logger("--games show start")
# 防止评测中途ajaxE被取消;3改成0是为了处理首次进入下一关的问题
@ -91,7 +88,6 @@ class GamesController < ApplicationController
end
end
def jupyter
# Jupyter没有challenge
@myshixun = Myshixun.find_by_identifier params[:identifier]
@ -103,7 +99,7 @@ class GamesController < ApplicationController
begin
@tpm_modified = @myshixun.repository_is_modified(@shixun.repo_path) # 判断TPM和TPI的版本库是否被改了
rescue
uid_logger("实训平台繁忙繁忙等级81")
uid_logger("服务器出现问题,请重置刷新页面")
end
end

@ -1,10 +1,10 @@
class HackUserLastestCodesController < ApplicationController
before_action :require_login, except: [:listen_result]
before_action :find_my_hack, only: [:show, :code_debug, :code_submit, :update_code, :sync_code,
before_action :find_my_hack, only: [:show, :code_debug, :code_submit, :update_code, :sync_code, :add_notes,
:listen_result, :result, :submit_records, :restore_initial_code]
before_action :update_user_hack_status, only: [:code_debug, :code_submit]
before_action :require_auth_identity, only: [:update_code, :restore_initial_code, :sync_code]
before_action :require_manager_identity, only: [:update_code]
before_action :require_auth_identity, only: [:add_notes]
before_action :require_manager_identity, only: [:show, :update_code, :restore_initial_code, :sync_code]
def show
@my_hack.update_attribute(:submit_status, 0) if @my_hack.submit_status == 1
@ -61,9 +61,11 @@ class HackUserLastestCodesController < ApplicationController
# 提交记录
def submit_records
@records = @my_hack.hack_user_codes.created_order
end
records = @my_hack.hack_user_codes
@records_count = records.count
@records = paginate records.created_order
end
# 提交记录详情
def record_detail
@ -106,6 +108,11 @@ class HackUserLastestCodesController < ApplicationController
end
def add_notes
@my_hack.update_attribute(:notes, params[:notes])
render_ok
end
private
def find_my_hack
@my_hack = HackUserLastestCode.find_by(identifier: params[:identifier])
@ -163,12 +170,12 @@ class HackUserLastestCodesController < ApplicationController
# 通关
if submit_params[:status] == "0"
# 编程题已经发布,且之前未通关奖励积分
@hack.increment!(:pass_num)
if @hack.status == 1 && !@my_hack.passed?
reward_attrs = { container_id: game.id, container_type: 'Hack', score: @hack.score }
RewardGradeService.call(@my_hack.user, reward_attrs)
RewardExperienceService.call(@my_hack.user, reward_attrs)
# 评测完成更新通过数
@hack.increment!(:pass_num)
@my_hack.update_attributes(passed: true, passed_time: Time.now)
end
end

@ -98,12 +98,22 @@ class HacksController < ApplicationController
# 发布功能
def publish
@hack.update_attribute(:status, 1)
base_attrs = {
trigger_user_id: current_user.id, viewed: 0, tiding_type: 'System', user_id: @hack.user_id,
parent_container_type: "HackPublish"
}
@hack.tidings.create!(base_attrs)
render_ok
end
# 取消发布
def cancel_publish
@hack.update_attribute(:status, 0)
base_attrs = {
trigger_user_id: current_user.id, viewed: 0, tiding_type: 'System', user_id: @hack.user_id,
parent_container_type: "HackUnPublish"
}
@hack.tidings.create!(base_attrs)
render_ok
end
@ -121,8 +131,19 @@ class HacksController < ApplicationController
def new;end
def destroy
@hack.destroy
render_ok
begin
base_attrs = {
user_id: @hack.user_id, viewed: 0, tiding_type: 'System', trigger_user_id: current_user.id,
parent_container_type: "HackDelete", extra: "#{@hack.name}"
}
@hack.tidings.create!(base_attrs)
@hack.destroy
render_ok
rescue => e
logger.error("####hack_delete_error: #{e.message}")
render_error("删除失败")
end
end
private
@ -167,10 +188,13 @@ class HacksController < ApplicationController
def param_update_sets sets, all_sets_id
delete_set_ids = all_sets_id - sets.map{|set|set[:id]}
@hack.hack_sets.where(id: delete_set_ids).destroy_all
logger.info("#######sets:#{sets}")
sets.each do |set|
logger.info("###set[:id] #{set[:id]}")
logger.info("###all_sets #{all_sets_id.include?(set[:id])}")
if all_sets_id.include?(set[:id])
update_attrs = {input: set[:input], output: set[:output], position: set[:position]}
@hack.hack_sets.find_by!(id: set[:id]).update_attributes(update_attrs)
@hack.hack_sets.find_by!(id: set[:id]).update_attributes!(update_attrs)
end
end
end

@ -80,4 +80,17 @@ class JupytersController < ApplicationController
render json: {status: 0}
end
def timeinfo_with_tpm
shixun = Shixun.find_by(identifier: params[:identifier])
info = jupyter_timeinfo_tpm(shixun)
render json: {status: 0}.merge(info)
end
def timeinfo_with_tpi
myshixun = Myshixun.find_by(identifier: params[:identifier])
info = jupyter_timeinfo_tpi(shixun)
render json: {status: 0}.merge(info)
end
end

@ -376,7 +376,7 @@ class MyshixunsController < ApplicationController
# todo: identifier 是以前的密码,用来验证的,新版如果不需要,和中间层协调更改.
params = {tpiID: "#{@myshixun.try(:id)}", tpiGitURL: "#{git_myshixun_url}", tpmGitURL: "#{git_shixun_url}",
identifier: "xinhu1ji2qu3"}
uri = "#{shixun_tomcat}/bridge/game/resetTpmRepository"
uri = "#{shixun_tomcat}/bridge/game/resetJupyterTpm"
res = uri_post uri, params
if (res && res['code'] != 0)
tip_exception("实训云平台繁忙繁忙等级95")

@ -5,6 +5,6 @@ class ShixunListsController < ApplicationController
private
def search_params
params.permit(:keyword, :type, :page, :limit, :order, :status, :diff)
params.permit(:keyword, :type, :page, :limit, :order, :status, :diff, :sort)
end
end

@ -215,7 +215,8 @@ class ShixunsController < ApplicationController
if @shixun.shixun_info.present?
ShixunInfo.create!(shixun_id: @new_shixun.id,
description: @shixun.description,
evaluate_script: @shixun.evaluate_script)
evaluate_script: @shixun.evaluate_script,
fork_reason: params[:reason].to_s.strip)
end
# 同步私密版本库
@ -265,8 +266,20 @@ class ShixunsController < ApplicationController
# 如果是jupyter先创建一个目录,为了挂载(因为后续数据集开启Pod后环境在没销毁前你上传数据集是挂载不上目录的因此要先创建目录方便中间层挂载)
if @new_shixun.is_jupyter?
folder = EduSetting.get('shixun_folder')
raise "存储目录未定义" unless folder.present?
path = "#{folder}/#{@new_shixun.identifier}"
FileUtils.mkdir_p(path, :mode => 0777) unless File.directory?(path)
# 复制数据集
save_path = File.join(folder, @shixun.identifier)
@shixun.data_sets.each do |set|
new_date_set = Attachment.new
new_date_set.attributes = set.attributes.dup.except("id", "container_id", "disk_directory")
new_date_set.container_id = @new_shixun.id
new_date_set.disk_directory = @new_shixun.identifier
new_date_set.save!
FileUtils.cp("#{save_path}/#{set.relative_path_filename}", path)
end
end
# 同步复制关卡
if @shixun.challenges.present?
@ -350,7 +363,7 @@ class ShixunsController < ApplicationController
page = params[:page] || 1
limit = params[:limit] || 10
@member_count = @shixun.shixun_members.count
@members = @shixun.shixun_members.includes(:user).page(page).per(limit)
@members = @shixun.shixun_members.order("role = 1 desc, created_at asc").includes(:user).page(page).per(limit)
end
def fork_list
@ -454,7 +467,13 @@ class ShixunsController < ApplicationController
def update_learn_setting
begin
ActiveRecord::Base.transaction do
@shixun.update_attributes!(shixun_params)
update_params =
if params[:shixun][:vnc]
shixun_params.merge(vnc_evaluate: 1)
else
shixun_params
end
@shixun.update_attributes!(update_params)
end
rescue => e
uid_logger_error("实训学习页面设置失败--------#{e.message}")
@ -781,7 +800,7 @@ class ShixunsController < ApplicationController
end
rescue => e
uid_logger_error(e.message)
tip_exception("#{e.message}")
tip_exception("服务器出现问题,请重置环境")
end
end
@ -896,16 +915,20 @@ class ShixunsController < ApplicationController
# 搜索成员
if request.get?
@collaborators = @shixun.shixun_members.where("user_id != #{@shixun.user_id}")
else
if params[:user_id]
man_member = ShixunMember.where(:shixun_id => @shixun.id, :user_id => @shixun.user_id).first
cha_member = ShixunMember.where(:user_id => params[:user_id], :shixun_id => @shixun.id).first
if man_member && cha_member
man_member.update_attribute(:role, 2)
cha_member.update_attribute(:role, 1)
@shixun.update_attribute(:user_id, cha_member.user_id)
end
end
else
begin
raise("请先选择成员") if params[:user_id].blank?
man_member = ShixunMember.where(:shixun_id => @shixun.id, :user_id => @shixun.user_id).first
cha_member = ShixunMember.where(:user_id => params[:user_id], :shixun_id => @shixun.id).first
if man_member && cha_member
man_member.update_attribute(:role, 2)
cha_member.update_attribute(:role, 1)
@shixun.update_attribute(:user_id, cha_member.user_id)
end
rescue => e
logger.error("######change_manager_error: #{e.message}")
render_error(e.message)
end
end
end

@ -214,7 +214,15 @@ class SubjectsController < ApplicationController
GitService.add_repository(repo_path: repo_path)
# todo: 为什么保存的时候要去除后面的.git呢??
@shixun.update_column(:repo_name, repo_path.split(".")[0])
mirror_id = MirrorRepository.find_by(type_name: 'Python3.6')&.id
mirror_id =
if @shixun.is_jupyter?
folder = EduSetting.get('shixun_folder')
path = "#{folder}/#{identifier}"
FileUtils.mkdir_p(path, :mode => 0777) unless File.directory?(path)
MirrorRepository.where("type_name like '%Jupyter%'").first&.id
else
MirrorRepository.find_by(type_name: 'Python3.6')&.id
end
if mirror_id
ShixunMirrorRepository.create!(:shixun_id => @shixun.id, :mirror_repository_id => mirror_id)
@shixun.shixun_service_configs.create!(:shixun_id => @shixun.id, :mirror_repository_id => mirror_id)

@ -31,6 +31,9 @@ module GradeDecorator
when 'check_ta_answer' then
game = Game.find_by(id: container_id)
game.present? ? "查看实训“#{game.challenge.shixun.name}”第#{game.challenge.position}关的TA人解答消耗的金币" : ''
when 'hack' then
hack = Hack.find_by(id: container_id)
hack.present? ? "完成了题目解答“#{hack.name}”,获得金币奖励:#{hack.score}" : ''
end
end
end

@ -220,11 +220,13 @@ module TidingDecorator
when 'Journal' then
message = parent_container&.notes.present? ? '' + message_content_helper(parent_container.notes) : ''
I18n.t(locale_format(parent_container_type)) % message
when 'Hack' then
I18n.t(locale_format(parent_container_type)) % parent_container.name
end
end
def discuss_content
I18n.t(locale_format(container.parent_id.present?)) % message_content_helper(container.content)
I18n.t(locale_format(parent_container_type, container.parent_id.present?)) % message_content_helper(container.content)
end
def grade_content
@ -250,6 +252,9 @@ module TidingDecorator
when 'shixunPublish' then
name = Shixun.find_by(id: parent_container_id)&.name || '---'
I18n.t(locale_format(parent_container_type)) % [name, container.score]
when 'Hack' then
name = Hack.find_by(id: container_id)&.name || '---'
I18n.t(locale_format(parent_container_type)) % [name, container.score]
else
I18n.t(locale_format(parent_container_type)) % container.score
end
@ -405,4 +410,8 @@ module TidingDecorator
def subject_start_course_content
I18n.t(locale_format) % belong_container&.name
end
def hack_content
I18n.t(locale_format(parent_container_type)) % (container&.name || extra)
end
end

@ -64,7 +64,7 @@ module ApplicationHelper
shixun_id = shixun_id.blank? ? -1 : shixun_id.join(",")
Shixun.select([:id, :name, :user_id, :challenges_count, :myshixuns_count, :trainee, :identifier]).where("id
in(#{shixun_id})").unhidden.order("homepage_show asc, myshixuns_count desc").limit(3)
in(#{shixun_id})").unhidden.publiced.order("homepage_show asc, myshixuns_count desc").limit(3)
end

@ -10,7 +10,7 @@ class Discuss < ApplicationRecord
has_one :praise_tread_cache, as: :object, dependent: :destroy
belongs_to :dis, polymorphic: true
belongs_to :challenge
belongs_to :challenge, optional: true
after_create :send_tiding
scope :children, -> (discuss_id){ where(parent_id: discuss_id).includes(:user).reorder(created_at: :asc) }
@ -52,11 +52,19 @@ class Discuss < ApplicationRecord
private
def send_tiding
if dis_type == 'Shixun'
user_id = has_parent? ? parent.user_id : Challenge.find(challenge_id).user_id
parent_container_type = 'Challenge'
challenge_id = challenge_id
elsif dis_type == 'Hack'
user_id = has_parent? ? parent.user_id : Hack.find(dis_id).user_id
parent_container_type = 'Hack'
challenge_id = dis_id
end
base_attrs = {
trigger_user_id: user_id, parent_container_id: challenge_id, parent_container_type: 'Challenge',
belong_container_id: dis_id, belong_container_type: 'Shixun', viewed: 0, tiding_type: 'Comment'
trigger_user_id: user_id, parent_container_id: challenge_id, parent_container_type: parent_container_type,
belong_container_id: dis_id, belong_container_type: dis_type, viewed: 0, tiding_type: 'Comment'
}
user_id = has_parent? ? parent.user_id : Challenge.find(challenge_id).user_id
tidings.create!(base_attrs.merge(user_id: user_id))
end
end

@ -11,6 +11,11 @@ class Hack < ApplicationRecord
has_many :hack_codes, :dependent => :destroy
has_many :hack_user_lastest_codes, :dependent => :destroy
has_many :discusses, as: :dis, dependent: :destroy
# 点赞
has_many :praise_treads, as: :praise_tread_object, dependent: :destroy
# 消息
has_many :tidings, as: :container
belongs_to :user
scope :published, -> { where(status: 1) }

@ -1,5 +1,5 @@
class HackSet < ApplicationRecord
#validates :input, presence: { message: "测试集输入不能为空" }
validates :input, presence: { message: "测试集输入不能为空" }
validates :output, presence: { message: "测试集输出不能为空" }
validates_uniqueness_of :input, scope: [:hack_id, :input], message: "多个测试集的输入不能相同"
# 编程题测试集

@ -12,4 +12,6 @@ class HackUserLastestCode < ApplicationRecord
scope :mine_hack, ->(author_id){ where(user_id: author_id) }
scope :passed, -> {where(status: 1)}
validates_length_of :notes, maximum: 5000, message: "笔记不能超过5000个字"
end

@ -12,7 +12,7 @@ class PraiseTread < ApplicationRecord
case self.praise_tread_object_type
when "Memo","Message","Issue"
self.tidings << Tiding.new(:trigger_user_id => self.user_id, :user_id => self.praise_tread_object.author_id, :parent_container_id => self.praise_tread_object_id, :parent_container_type => self.praise_tread_object_type, :viewed => 0, :tiding_type => "Praise")
when "Discuss","Challenge","HomeworkCommon","JournalsForMessage","Journal","GraduationTopic","GraduationTask"
when "Discuss","Challenge","HomeworkCommon","JournalsForMessage","Journal","GraduationTopic","GraduationTask", "Hack"
self.tidings << Tiding.new(:trigger_user_id => self.user_id, :user_id => self.praise_tread_object.user_id, :parent_container_id => self.praise_tread_object_id, :parent_container_type => self.praise_tread_object_type, :viewed => 0, :tiding_type => "Praise")
end
end

@ -16,7 +16,9 @@ module Searchable::Shixun
name: name,
description: Util.extract_content(description)[0..Searchable::MAXIMUM_LENGTH],
status: status,
myshixuns_count: myshixuns_count
myshixuns_count: myshixuns_count,
created_at: created_at,
publish_time: publish_time
}.merge!(searchable_user_data)
.merge!(searchable_challenge_data)
end

@ -103,6 +103,19 @@ class Shixun < ApplicationRecord
shixun_info.try(:evaluate_script)
end
def fork_reason
case shixun_info.try(:fork_reason)
when 'Shixun'
'实训内容升级'
when 'Course'
'课堂教学使用'
when 'Subject'
'实践课程使用'
else
shixun_info.try(:fork_reason)
end
end
def fork_identifier
self.fork_from.nil? ? "--" : fork_shixuns.first&.identifier
end

@ -1,6 +1,7 @@
class ShixunInfo < ApplicationRecord
belongs_to :shixun
validates_uniqueness_of :shixun_id
validates_length_of :fork_reason, maximum: 60
after_commit :create_diff_record
private

@ -13,25 +13,24 @@ class Admins::ShixunQuery < ApplicationQuery
all_shixuns = Shixun.all
status =
case params[:status]
when "editing" then [0]
when "pending" then [1]
when "processed" then [2]
when "closed" then [3]
else
[0,1,2,3]
when "editing" then {status: 0}
when "processed" then {status: 2, public: 0}
when "pending" then {public: 1}
when "publiced" then {public: 2}
when "closed" then {status: 3}
end
public =
case params[:public]
when "editing" then [0]
when "pending" then [1]
when "processed" then [2]
else
[0,1,2]
end
all_shixuns = all_shixuns.where(status) if status.present?
all_shixuns = all_shixuns.where(status: status) if status.present?
all_shixuns = all_shixuns.where(public: public) if public.present?
if params[:fork_status].present?
all_shixuns = all_shixuns.where.not(fork_from: nil)
case params[:fork_status]
when 'Shixun', 'Course', 'Subject'
all_shixuns = all_shixuns.joins(:shixun_info).where(shixun_infos: {fork_reason: params[:fork_status]})
when 'Other'
all_shixuns = all_shixuns.joins(:shixun_info).where("fork_reason is null or fork_reason not in ('Shixun', 'Course', 'Subject')")
end
end
if params[:tag].present?
all_shixuns = all_shixuns.joins(:mirror_repositories).where("mirror_repositories.id = ?",params[:tag].to_i)

@ -15,16 +15,15 @@ class Admins::ShixunSettingsQuery < ApplicationQuery
all_shixuns = all_shixuns.where(id: params[:id]) if params[:id].present?
status =
case params[:status]
when "editing" then [0]
when "pending" then [1]
when "processed" then [2]
when "closed" then [3]
else
[0,1,2,3]
end
all_shixuns = all_shixuns.where(status: status) if status.present?
case params[:status]
when "editing" then {status: 0}
when "processed" then {status: 2, public: 0}
when "pending" then {public: 1}
when "publiced" then {public: 2}
when "closed" then {status: 3}
end
all_shixuns = all_shixuns.where(status) if status.present?
if params[:tag].present?
all_shixuns = all_shixuns.joins(:mirror_repositories).where("mirror_repositories.id = ?",params[:tag].to_i)

@ -213,4 +213,27 @@ module JupyterService
_jupyter_active(tpiID)
end
def _jupyter_timeinfo(tpiID)
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/getTimeInfo"
params = {:tpiID => tpiID}
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级130")
end
res['data']
end
# 获取时间参数
def jupyter_timeinfo_tpm(shixun)
tpiID = "tpm#{shixun.id}"
_jupyter_timeinfo(tpiID)
end
# 获取时间参数
def jupyter_timeinfo_tpi(myshixun)
tpiID = myshixun.id
_jupyter_timeinfo(tpiID)
end
end

@ -33,6 +33,10 @@ class ShixunSearchService < ApplicationService
@shixuns = status == "published" ? @shixuns.where(status: 2) : @shixuns.where(status: [0, 1])
end
if params[:no_jupyter]
@shixuns = @shixuns.where(is_jupyter: 0)
end
## 筛选 难度
if params[:diff].present? && params[:diff].to_i != 0
@shixuns = @shixuns.where(trainee: params[:diff])
@ -49,12 +53,16 @@ class ShixunSearchService < ApplicationService
includes: [ :shixun_info, :challenges, :subjects, user: { user_extension: :school } ]
}
model_options.merge!(where: { id: @shixuns.pluck(:id) })
model_options.merge!(order: {"myshixuns_count" => sort_str})
model_options.merge!(order: {sort_str => order_str})
model_options.merge!(default_options)
model_options
end
def sort_str
def order_str
params[:order] || "desc"
end
def sort_str
params[:sort] || "myshixuns_count"
end
end

@ -79,7 +79,7 @@ class Subjects::CopySubjectService < ApplicationService
copy_shixun_service_configs_data!(shixun, to_shixun)
copy_challenges_data!(shixun, to_shixun)
copy_shixun_members_data!(to_shixun)
copy_jupyter_data_sets(shixun, to_shixun) if shixun.is_jupyter?
# 云上实验室
if laboratory
laboratory.laboratory_shixuns.create(shixun: to_shixun)
@ -87,6 +87,25 @@ class Subjects::CopySubjectService < ApplicationService
to_shixun
end
# 复制jupyter的数据集
def copy_jupyter_data_sets(shixun, to_shixun)
return unless shixun.is_jupyter?
folder = EduSetting.get('shixun_folder')
raise "存储目录未定义" unless folder.present?
path = "#{folder}/#{to_shixun.identifier}"
FileUtils.mkdir_p(path, :mode => 0777) unless File.directory?(path)
# 复制数据集
save_path = File.join(folder, shixun.identifier)
shixun.data_sets.each do |set|
new_date_set = Attachment.new
new_date_set.attributes = set.attributes.dup.except("id", "container_id", "disk_directory")
new_date_set.container_id = to_shixun.id
new_date_set.disk_directory = to_shixun.identifier
new_date_set.save!
FileUtils.cp("#{save_path}/#{set.relative_path_filename}", path)
end
end
# 创建实训长字段内容
def copy_shixun_info_data!(shixun, to_shixun)
to_shixun_info = ShixunInfo.new

@ -65,13 +65,18 @@ class Users::ShixunService
end
def manage_shixun_status_filter(relations)
status = case params[:status]
when 'editing' then 0
when 'applying' then 1
when 'published' then 2
when 'closed' then 3
end
relations = relations.where(status: status) if status
if params[:status] == "publiced"
relations = relations.where(public: 2)
elsif params[:status] == "applying"
relations = relations.where(public: 1)
else
status = case params[:status]
when 'editing' then 0
when 'published' then 2
when 'closed' then 3
end
relations = relations.where(status: status) if status
end
relations
end

@ -8,7 +8,7 @@
<div class="d-flex position-r">
<div class="form-group mr-2">
<label for="status">状态:</label>
<% status_options = [['全部', ''], ["编辑中(#{@editing_shixuns})", "editing"], ["待审核(#{@pending_shixuns})", 'pending'], ["已发布(#{@processed_shixuns})", 'processed'],["已关闭(#{@closed_shixuns})",'closed']] %>
<% status_options = [['全部', ''], ["编辑中(#{@editing_shixuns})", "editing"], ["已发布未公开(#{@processed_shixuns})", 'processed'], ["待审核(#{@pending_public_shixuns})", 'pending'], ["已公开(#{@processed_pubic_shixuns})", 'publiced'], ["已关闭(#{@closed_shixuns})",'closed']] %>
<%= select_tag(:status, options_for_select(status_options), class: 'form-control') %>
</div>
<div class="form-group mr-2">

@ -4,33 +4,42 @@
<div class="box search-form-container shixuns-list-form">
<%= form_tag(admins_shixuns_path, method: :get, class: 'form-inline search-form',id:"shixuns-search-form",remote:true) do %>
<div class="form-group">
<label for="status">状态:</label>
<% status_options = [['全部', ''], ["编辑中(#{@editing_shixuns})", "editing"], ["待审核(#{@pending_shixuns})", 'pending'], ["已发布(#{@processed_shixuns})", 'processed'],["已关闭(#{@closed_shixuns})",'closed']] %>
<%= select_tag(:status, options_for_select(status_options), class: 'form-control') %>
</div>
<div class="d-flex flex-column w-100">
<div class="d-flex position-r">
<div class="form-group">
<label for="status">状态:</label>
<% status_options = [['全部', ''], ["编辑中(#{@editing_shixuns})", "editing"], ["已发布未公开(#{@processed_shixuns})", 'processed'], ["待审核(#{@pending_public_shixuns})", 'pending'], ["已公开(#{@processed_pubic_shixuns})", 'publiced'], ["已关闭(#{@closed_shixuns})",'closed']] %>
<%= select_tag(:status, options_for_select(status_options), class: 'form-control') %>
</div>
<div class="form-group">
<label for="status">公开:</label>
<% public_options = [['全部', ''], ["未公开(#{@none_public_shixuns})", "editing"], ["待审核(#{@pending_public_shixuns})", 'pending'], ["已公开(#{@processed_pubic_shixuns})", 'processed']] %>
<%= select_tag(:public, options_for_select(public_options), class: 'form-control') %>
</div>
<div class="form-group mr-2">
<label for="tag-choosed">技术平台:</label>
<%= select_tag(:tag, options_for_select(@shixuns_type_check.unshift(["",nil])), class: 'form-control',id:"tag-choosed") %>
</div>
<div class="form-group mr-2">
<label for="tag-choosed">技术平台:</label>
<%= select_tag(:tag, options_for_select(@shixuns_type_check.unshift(["",nil])), class: 'form-control',id:"tag-choosed") %>
</div>
<div class="form-group mr-2">
<label>搜索类型:</label>
<% auto_trial_options = [['创建者姓名', 0], ['实训名称', 1], ['学校名称', 2]] %>
<%= select_tag(:search_type, options_for_select(auto_trial_options), class: 'form-control') %>
</div>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2 ml-3', placeholder: '输入关键字搜索') %>
<div class="">
<a href="javascript:void(0)" class="btn btn-primary" id="shixuns-export" data-disable-with = '导出中...'>导出</a>
</div>
</div>
<div class="d-flex mt-3">
<div class="form-group">
<label for="status">fork原因</label>
<% fork_status_options = [['全部', ''], ["全部fork实训", "Fork"], ["实训内容升级", 'Shixun'], ["课堂教学使用", 'Course'],["实践课程使用",'Subject'],["其他原因",'Other']] %>
<%= select_tag(:fork_status, options_for_select(fork_status_options), class: 'form-control') %>
</div>
<div class="form-group">
<label>搜索类型:</label>
<% auto_trial_options = [['创建者姓名', 0], ['实训名称', 1], ['学校名称', 2]] %>
<%= select_tag(:search_type, options_for_select(auto_trial_options), class: 'form-control') %>
<%= submit_tag('搜索', class: 'btn btn-primary ml-3','data-disable-with': '搜索中...') %>
<%= link_to "清除", admins_shixuns_path,class: "btn btn-default",id:"shixuns-clear-search",'data-disable-with': '清除中...' %>
</div>
</div>
<%= text_field_tag(:keyword, params[:keyword], class: 'form-control col-sm-2', placeholder: '输入关键字搜索') %>
<%= submit_tag('搜索', class: 'btn btn-primary','data-disable-with': '搜索中...') %>
<%= link_to "清除", admins_shixuns_path,class: "btn btn-default",id:"shixuns-clear-search",'data-disable-with': '清除中...' %>
<% end %>
<a href="javascript:void(0)" class="btn btn-primary" id="shixuns-export" data-disable-with = '导出中...'>导出</a>
</div>
<div class="box admin-list-container shixuns-list-container">

@ -2,17 +2,18 @@
<thead class="thead-light">
<th width="4%">序号</th>
<th width="8%">ID</th>
<th width="24%" class="text-left">实训名称</th>
<th width="22%" class="text-left">实训名称</th>
<th width="8%">技术平台</th>
<th width="5%">Fork源</th>
<th width="10%">Fork原因</th>
<th width="5%">实践</th>
<th width="5%">选择</th>
<th width="4%">选择</th>
<th width="6%">状态</th>
<th width="6%">公开</th>
<th width="7%">创建者</th>
<th width="11%"><%= sort_tag('创建于', name: 'created_at', path: admins_shixuns_path) %></th>
<th width="5%">单测</th>
<th width="6%">操作</th>
<th width="6%">创建者</th>
<th width="8%"><%= sort_tag('创建于', name: 'created_at', path: admins_shixuns_path) %></th>
<th width="4%">单测</th>
<th width="4%">操作</th>
</thead>
<tbody>
<% if shixuns.present? %>
@ -31,6 +32,7 @@
<%= link_to shixun.try(:identifier), shixun_path(shixun.try(:identifier)), target: '_blank'%>
<% end%>
</td>
<td><%= overflow_hidden_span(shixun&.fork_reason, width: 150) %></td>
<td><%= shixun.challenges.where(:st => 0).size %></td>
<td><%= shixun.challenges.where(:st => 1).size %></td>
<td class="shixuns-status-<%= shixun.status %>"><%= shixun_authentication_status shixun %></td>

@ -5,14 +5,16 @@ json.chooses do
json.type choose.category
end
end
json.st @challenge.st
if @tab == 0
# 本关任务tab的编辑模式
json.(@challenge, :id, :subject, :task_pass, :difficulty, :score, :exec_time)
json.(@challenge, :id, :subject, :task_pass, :difficulty, :score, :exec_time, :st)
json.tags @challenge.challenge_tags.map(&:name)
elsif @tab == 1
# 评测设置的编辑模式
json.(@challenge, :id, :path, :exec_path, :show_type, :original_picture_path, :expect_picture_path, :picture_path,
:web_route, :test_set_score, :test_set_average)
:web_route, :test_set_score, :test_set_average, :exec_time)
json.has_web_route @shixun.has_web_route?
json.test_sets @challenge.test_sets do |set|
json.hidden (set.is_public ? 0 : 1)

@ -1,7 +1,7 @@
json.(@challenge_choose, :challenge_id, :subject, :answer,
:standard_answer, :score, :difficult,
:position, :category)
json.st @challenge.st
# 选项的参数
json.choose_contents @challenge_choose.challenge_questions do |question|
json.(question, :option_name, :position, :right_key)

@ -1,6 +1,6 @@
# 导航栏公共数据
json.partial! "challenges/top_common_data", shixun_identifier: @shixun.identifier
json.(@challenge, :id, :subject, :task_pass, :difficulty, :score, :exec_time)
json.(@challenge, :id, :subject, :task_pass, :difficulty, :score, :exec_time, :st)
json.tags @challenge.challenge_tags.map(&:name)

@ -1,11 +1,16 @@
json.author do
json.partial! 'users/user', user: discuss.user
end
json.id discuss.id
json.content content_safe(discuss.content)
json.time time_from_now(discuss.created_at)
json.hack_id discuss.dis_id
json.hidden discuss.hidden
# 主贴和回复有一些不同点
if discuss.parent_id
json.can_delete discuss.can_deleted?(current_user)
else
json.praise_count discuss.praises_count
json.user_praise discuss.praise_treads.select{|pt| pt.user_id == current_user.id}.length > 0
json.can_delete discuss.can_deleted?(current_user) && child.count == 0
end

@ -1,6 +1,6 @@
json.disscuss_count @disscuss_count
json.disscuss_count @discusses_count
json.comments @discusses do |discuss|
json.partial! 'comments/discuss', locals: { discuss: discuss}
json.partial! 'comments/discuss', locals: { discuss: discuss, child: discuss.child_discuss(current_user)}
json.children discuss.child_discuss(current_user) do |c_d|
json.partial! 'comments/discuss', locals: { discuss: c_d }
end

@ -0,0 +1 @@
json.praise_count @praise_count

@ -1,8 +1,12 @@
json.(@hack_user, :id, :status, :error_line, :error_msg, :expected_output,
:input, :output, :execute_time, :execute_memory, :created_at, :code)
json.language @hack_user.hack.language
json.name @hack_user.hack.name
json.myproblem_identifier @my_hack.identifier
json.user do
json.partial! 'users/user', user: current_user
end
json.status 0
json.message "返回成功"
json.data do
json.(@hack_user, :id, :status, :error_line, :error_msg, :expected_output,
:input, :output, :execute_time, :execute_memory, :created_at, :code)
json.language @hack_user.hack.language
json.name @hack_user.hack.name
json.myproblem_identifier @my_hack.identifier
json.user do
json.partial! 'users/user', user: current_user
end
end

@ -1,5 +1,5 @@
json.status 0
json.message "评测成"
json.message "评测成"
json.data do
json.(@result, :id, :status, :error_line, :error_msg,
:input, :output, :execute_time, :execute_memory)

@ -1,11 +1,14 @@
json.hack do
json.(@hack, :name, :difficult, :time_limit, :description, :score, :identifier, :status)
json.(@hack, :id, :name, :difficult, :time_limit, :description, :score, :identifier, :status, :praises_count)
json.language @hack.language
json.username @hack.user.real_name
json.code @my_hack.code
json.pass_count @hack.pass_num
json.submit_count @hack.submit_num
json.notes @my_hack.notes
json.modify_code @modify
json.comments_count @hack.discusses.count
json.user_praise @hack.praise_treads.select{|pt| pt.user_id == current_user.id}.length > 0
end
json.test_case do
@ -15,5 +18,6 @@ end
json.user do
json.partial! 'users/user', user: current_user
json.hack_manager @hack.manager?(current_user)
json.admin current_user.admin_or_business?
end

@ -1,4 +1,8 @@
json.array! @records do |hack_user|
json.(hack_user, :id, :created_at, :status, :execute_time, :execute_memory)
json.language hack_user.hack.language
end
json.records do
json.array! @records do |hack_user|
json.(hack_user, :id, :created_at, :status, :execute_time, :execute_memory)
json.language hack_user.hack.language
end
end
json.records_count @records_count

@ -5,7 +5,10 @@ json.data_sets do
json.author set.author.real_name
json.created_on set.created_on
json.filesize number_to_human_size(set.filesize)
json.file_path "#{@absolute_folder}/#{set.relative_path_filename}"
# 这里需要去除shixunfiles目录后的标识因为Jupyter代码里面会写死这样的路径当实训fork后这个版本库的路径无法修改因此给用户展示的
# 还是/data/shixunfiles/+文件名这种形式
json.file_path "#{@absolute_folder}/#{set.relative_path_filename}".gsub("/#{@shixun.identifier}", "")
end
end
json.data_sets_count @data_count
json.data_sets_count @data_count
json.folder_name @absolute_folder

@ -93,6 +93,7 @@
Challenge:
"1_end": "赞了你发布的实训任务:%s第%s关"
"2_end": "踩了你发布的实训任务:%s第%s关"
Hack_end: "赞了你发布的题目:%s"
Memo:
true_end: "赞了你的评论:%s"
false_end: "赞了发布的帖子:%s"
@ -107,8 +108,16 @@
Issue_end: "赞了你发布的项目Issue%s"
Journal_end: "赞了你的回复%s"
Discuss:
true_end: "评论了你的回复:%s"
false_end: "评论了你发布的实训:%s"
Challenge:
true_end: "评论了你的回复:%s"
false_end: "评论了你发布的实训:%s"
Hack:
true_end: "评论了你的回复:%s"
false_end: "评论了你发布的题目:%s"
Hack:
HackPublish_end: "你发布了题目:%s"
HackUnPublish_end: "你撤销发布了题目:%s"
HackDelete_end: "你删除了题目:%s"
Grade:
Avatar_end: "首次上传头像获得金币奖励:%s金币"
Phone_end: "首次绑定手机号码获得金币奖励:%s金币"
@ -118,6 +127,7 @@
Answer:
true_end: "查看实训%s第%s关的参考答案消耗金币%s金币"
false_end: "查看实训的参考答案消耗金币:%s金币"
Hack_end: "完成题目解答:%s获得金币奖励%s金币"
Game_end: "通过实训%s的第%s关获得金币奖励%s金币"
Memo_end: "发布的评论或者帖子获得平台奖励:%s金币"
Discusses_end: "发布的评论获得金币奖励:%s金币"

@ -35,6 +35,8 @@ Rails.application.routes.draw do
get :reset_with_tpm
get :active_with_tpm
get :active_with_tpi
get :timeinfo_with_tpm
get :timeinfo_with_tpi
post :import_with_tpm
end
@ -69,7 +71,12 @@ Rails.application.routes.draw do
delete :delete_set
end
resources :comments do
post :reply
collection do
post :reply
end
member do
post :hidden
end
end
end
@ -83,6 +90,7 @@ Rails.application.routes.draw do
get :submit_records
post :restore_initial_code
post :sync_code
post :add_notes
end
collection do

@ -0,0 +1,5 @@
class AddForkReasonToShixunInfo < ActiveRecord::Migration[5.2]
def change
add_column :shixun_infos, :fork_reason, :string
end
end

@ -0,0 +1,8 @@
class ModifyTaskPassForChallenges < ActiveRecord::Migration[5.2]
def change
Challenge.find_each do |challenge|
task_pass = challenge.task_pass.gsub(" ", "").gsub("rac", '\frac')
challenge.update_attribute(:task_pass, task_pass)
end
end
end

@ -0,0 +1,6 @@
class AddPraisesCountForHacks < ActiveRecord::Migration[5.2]
def change
add_column :hacks, :praises_count, :integer, :default => 0
add_column :hacks, :comments_count, :integer, :default => 0
end
end

@ -0,0 +1,5 @@
class ModifyOpenOrNotForHacks < ActiveRecord::Migration[5.2]
def change
change_column :hacks, :open_or_not, :boolean, :default => false
end
end

@ -0,0 +1,5 @@
class AddNotesHackUserLastestCodes < ActiveRecord::Migration[5.2]
def change
add_column :hack_user_lastest_codes, :notes, :longtext
end
end

@ -0,0 +1,8 @@
class ModifyTaskPass2ForChallenges < ActiveRecord::Migration[5.2]
def change
challenges = Challenge.where("task_pass like '%frac%'")
challenges.find_each do |c|
c.update_column(:task_pass, c.task_pass.gsub('\f\frac', '\frac'))
end
end
end

@ -14,19 +14,19 @@ namespace :excellent_course_exercise do
course = Course.find_by(id: course_id)
course.exercises.each_with_index do |exercise, index|
# if exercise.exercise_users.where(commit_status: 1).count == 0
if exercise.exercise_users.where(commit_status: 1).count == 0
# 第一个试卷的参与人数和通过人数都是传的数据,后续的随机
if index == 0
members = course.students.order("id asc").limit(participant_count)
update_exercise_user(exercise, members, pass_count)
else
new_participant_count = rand((participant_count - 423)..participant_count)
new_pass_count = rand((new_participant_count - 113)..new_participant_count)
new_participant_count = rand((participant_count - 20)..participant_count)
new_pass_count = rand((new_participant_count - 30)..new_participant_count)
members = course.students.order("id asc").limit(new_participant_count)
update_exercise_user(exercise, members, new_pass_count)
end
# end
end
end
end
@ -36,14 +36,15 @@ namespace :excellent_course_exercise do
# index < pass_count 之前的学生都是通关的,之后的未通过
members.each_with_index do |member, index|
exercise_user = exercise.exercise_users.where(user_id: member.user_id).take
if exercise_question_ids.length == 20
question_length = exercise_question_ids.length
if question_length == 20
rand_num = index < pass_count - 1 ? rand(15..20) : rand(1..10)
elsif exercise_question_ids.length == 17
elsif question_length == 17
rand_num = index < pass_count - 1 ? rand(12..17) : rand(1..9)
elsif exercise_question_ids.length == 39
elsif question_length == 39
rand_num = index < pass_count - 1 ? rand(30..39) : rand(1..18)
else
rand_num = exercise_question_ids.length
rand_num = index < pass_count - 1 ? rand((question_length-3)..question_length) : rand(1..(question_length-8))
end
if exercise_user && exercise_user.commit_status == 0

@ -0,0 +1,32 @@
# 执行示例 RAILS_ENV=production bundle exec rake migrate_course_student:student args=3835,2526,21950,1000
# args 第一个课程 course_id第二个参数是学校school_id第三个参数是部门id第四个参数是迁移数量
#
desc "同步学校的学生"
namespace :migrate_course_student do
if ENV['args']
course_id = ENV['args'].split(",")[0] # 对应课堂的id
school_id = ENV['args'].split(",")[1] # 对应学校id
department_id = ENV['args'].split(",")[2] # 对应部门id
limit = ENV['args'].split(",")[3] # 限制导入的数量
end
task :student => :environment do
course = Course.find course_id
users = User.joins(:user_extension).where(user_extensions: {school_id: school_id, department_id: department_id, identity: 1}).limit(limit)
user_ids = []
users.each do |user|
if user.student_id.present? && !course.students.exists?(user_id: user.id)
begin
CourseMember.create!(course_id: course_id, user_id: user.id, role: 4)
user_ids << user.id
rescue Exception => e
Rails.logger(e.message)
end
end
end
CourseAddStudentCreateWorksJob.perform_later(course_id, user_ids)
end
end

@ -37,9 +37,9 @@ document.addEventListener('keydown', (e) => {
if(e.data==="stopParent"){
//重置停止
timebool=false;
console.log("父窗口调用停止");
// console.log("父窗口调用停止");
}else if(e.data==="clonsParent"){
console.log("父窗口调用启动");
// console.log("父窗口调用启动");
//取消启动
timebool=true;
// runEvery10Sec();

@ -30,7 +30,7 @@ const env = getClientEnvironment(publicUrl);
module.exports = {
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.s
// devtool: "cheap-module-eval-source-map",
devtool: "cheap-module-eval-source-map",
// 开启调试
//devtool: "source-map", // 开启调试
// These are the "entry points" to our application.

@ -1883,9 +1883,8 @@ a:hover.task_icons_close{background: url(../images/popup/sy_icons_close.png) -40
.newupload_nav li:last-child{ border-right: none;}
.newupload_nav li a{font-size:12px; color:#444;}
.newupload_nav_hover{ background: #3498db; }
.newupload_nav_nomal { }
.newupload_nav_hover a{color: #fff !important; }
.markdown-body { text-align: justify;word-break: break-all;}
.bor-reds{
border:1px solid #FF0000!important;
border-radius: 4px;
@ -1894,6 +1893,7 @@ a:hover.task_icons_close{background: url(../images/popup/sy_icons_close.png) -40
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
@charset "UTF-8";
/*!

@ -360,7 +360,7 @@ label.infolabel{display: block;float: left;width: 56px;text-align: right;margin-
.subshaicontent a{float: left;margin-right: 20px;color: #999;cursor: pointer}
.search-new{width: 248px;height:32px;position: relative;margin-right: 35px;}
.search-new{width: 248px;height:32px;position: relative;}
.search-span{display: block;position: absolute;width: 100%;height: 100%;left:0px;top:0px;background-color: #F4F4F4;border: 1px solid #EAEAEA; border-radius: 4px;z-index: 1}
.search-new-input{height: 32px;padding-left: 5px;width: 225px;border: none;box-sizing: border-box;background: none;outline: none;position: absolute;left:0px;top:1px;z-index: 2}
.search-new img,.search-new a,.search-new .searchicon{cursor: pointer;position: absolute;right:2px;top:2px;z-index: 2}
@ -2934,7 +2934,7 @@ a.singlepublishtwo{
padding: 40px !important;
}
.editormd-html-preview{
width: 94% !important;
width: 100% !important;
color: #323232 !important;
}
#homework_editorMd_description hr{
@ -3478,3 +3478,9 @@ a.singlepublishtwo{
/*width: auto !important;*/
/*max-width: 600px !important;*/
/*}*/
.markdown-body {
text-align: justify;
word-break: break-all;
}

@ -35,7 +35,7 @@ if (isDev) {
// 老师
//ebugType="teacher";
// 学生
// debugType="student";
//debugType="student";
window._debugType = debugType;
export function initAxiosInterceptors(props) {
@ -52,7 +52,7 @@ export function initAxiosInterceptors(props) {
//proxy="http://47.96.87.25:48080"
proxy="https://pre-newweb.educoder.net"
proxy="https://test-newweb.educoder.net"
proxy="https://test-jupyterweb.educoder.net"
// proxy="https://test-jupyterweb.educoder.net"
//proxy="http://192.168.2.63:3001"
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求

@ -3,22 +3,24 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 17:32:55
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 17:51:44
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 11:11:57
*/
import './index.scss';
import React, { useState } from 'react';
import { Form, Button, Input } from 'antd';
import QuillForEditor from '../../quillForEditor';
import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html'
// import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html'
// import {formatDelta} from './util';
const FormItem = Form.Item;
function CommentForm (props) {
const {
commentCtxChagne,
onCancel,
onSubmit,
form
form,
type
} = props;
const { getFieldDecorator } = form;
@ -34,22 +36,25 @@ function CommentForm (props) {
// const { form: { getFieldDecorator } } = props;
const [showQuill, setShowQuill] = useState(false);
// 点击输入框
const handleInputClick = () => {
const handleInputClick = (type) => {
setShowQuill(true);
}
// 取消
const handleCancle = () => {
setShowQuill(false);
onCancel && onCancel();
setCtx('');
props.form.resetFields();
onCancel && onCancel();
}
// 编辑器内容变化时
const handleContentChange = (content) => {
console.log('编辑器内容', content);
setCtx(content);
try {
const _html = new QuillDeltaToHtmlConverter(content.ops, {}).convert();
// const _html = new QuillDeltaToHtmlConverter(content.ops, {}).convert();
// props.form.setFieldsValue({'comment': _html.replace(/<\/?[^>]*>/g, '')});
props.form.setFieldsValue({'comment': _html});
props.form.setFieldsValue({'comment': content});
} catch (error) {
console.log(error);
}
@ -63,13 +68,26 @@ function CommentForm (props) {
const content = ctx;
props.form.setFieldsValue({'comment': ''});
setCtx('');
console.log(content);
onSubmit && onSubmit(content);
// const _html = formatDelta(content.ops);
console.log('保存的内容=====》》》》', content);
onSubmit && onSubmit(JSON.stringify(content));
}
});
}
const handleShowImage = (url) => {
alert(url);
}
// const _clazz = type === 'bottom' ? 'comment_form_bottom_area' : 'comment_form_area';
let _clazz;
if (type === 'bottom') {
_clazz = showQuill ? 'comment_form_bottom_area active' : 'comment_form_bottom_area';
} else {
_clazz = 'comment_form_area';
}
return (
<Form>
<Form className={_clazz}>
<FormItem>
{
getFieldDecorator('comment', {
@ -78,13 +96,13 @@ function CommentForm (props) {
],
})(
<Input
onClick={handleInputClick}
onClick={() => handleInputClick(type)}
placeholder="说点儿什么~"
className={showQuill ? '' : 'show_input'}
style={{
height: showQuill ? '0px' : '40px',
overflow: showQuill ? 'hidden' : 'auto',
opacity: showQuill ? 0 : 1,
transition: 'all .3s'
}}
/>
)
@ -98,14 +116,15 @@ function CommentForm (props) {
overflow: showQuill ? 'none' : 'hidden',
transition: 'all 0.3s'
}}
style={{ height: '150px', overflowY: 'auto' }}
style={{ height: '150px' }}
placeholder="说点儿什么~"
options={options}
value={ctx}
showUploadImage={handleShowImage}
onContentChange={handleContentChange}
/>
</FormItem>
<FormItem style={{ textAlign: 'right' }}>
<FormItem style={{ textAlign: 'right', display: showQuill ? 'block' : 'none' }}>
<Button onClick={handleCancle}>取消</Button>
<Button onClick={handleSubmit} type="primary" style={{ marginLeft: '10px'}}>发送</Button>
</FormItem>

@ -3,16 +3,19 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-12-18 10:49:46
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 11:39:23
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-25 10:03:21
*/
import './index.scss';
import React from 'react';
import { Icon } from 'antd';
// import { Icon } from 'antd';
// import MyIcon from '../MyIcon';
function CommentIcon ({
type, // 图标类型
count, // 评论数
iconClick,
iconColor,
theme,
...props
}) {
@ -21,10 +24,17 @@ function CommentIcon ({
iconClick && iconClick();
}
const _className = [undefined, null, ''].includes(count) ? 'comment_count_none' : 'comment_count';
const _classIcon = `iconfont icon-${type} icon_font_size_14 comment_icon `;
return (
<span className={`comment_icon_count ${props.className}`} onClick={ handleSpanClick }>
<Icon className="comment_icon" type={type} />
<span className="comment_count">{ count }</span>
<span
style={props.style}
className={`comment_icon_count ${props.className}`}
onClick={ handleSpanClick }
>
{/* <Icon className="comment_icon" type={type} style={{ color: iconColor }} theme={theme}/> */}
<span className={_classIcon} style={{ color: iconColor }}></span>
<span className={_className}>{ count }</span>
</span>
)
}

@ -3,104 +3,149 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 17:35:17
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 18:02:28
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 11:30:32
*/
import './index.scss';
import 'quill/dist/quill.core.css'; // 核心样式
import 'quill/dist/quill.snow.css'; // 有工具栏
import 'quill/dist/quill.bubble.css'; // 无工具栏
import 'katex/dist/katex.min.css'; // katex 表达式样式
import React, { useState } from 'react';
import CommentIcon from './CommentIcon';
import { getImageUrl, CNotificationHOC } from 'educoder'
import { Icon } from 'antd';
import moment from 'moment';
// import QuillForEditor from '../../quillForEditor';
import CommentForm from './CommentForm';
import QuillForEditor from '../../quillForEditor';
// import {ModalConfirm} from '../ModalConfirm';
function CommentItem ({
isAdmin,
options,
confirm
confirm,
comment,
submitDeleteComment,
submitChildComment,
likeComment,
showOrHideComment
}) {
// 显示评论输入框
const [showQuill, setShowQuill] = useState(false);
// 加载更多评论内容
const [showMore, setShowMore] = useState(false);
// const [showMore, setShowMore] = useState(false);
// 显示子列数
const [showItemCount, setShowItemCount] = useState(5);
// 箭头方向
const [arrow, setArrow] = useState(false);
// 删除评论
const deleteComment = () => {
console.log('删除评论...');
const {
author = {}, // 作者
id, // 评论id
content, // 回复内容
time, // 回复时间
hidden, // 是否隐藏
// hack_id, // OJ的ID
praise_count, // 点赞数
user_praise, // 当前用户是否点赞
can_delete,
children = [] // 子回复
} = comment;
// 删除评论 type: parent | child, id
const deleteComment = (id) => {
confirm({
title: '提示',
content: (<p>确定要删除该条回复吗?</p>),
content: ('确定要删除该条回复吗?'),
onOk () {
console.log('点击了删除');
console.log('点击了删除', id);
submitDeleteComment && submitDeleteComment(id);
}
});
// ModalConfirm('提示', (<p>确定要删除该条回复吗?</p>), () => {
// console.log('点击了删除');
// });
}
// 评论头像
const commentAvatar = (url) => (
<img className="item-flex flex-image" src='https://b-ssl.duitang.com/uploads/item/201511/13/20151113110434_kyReJ.jpeg' alt=""/>
const commentAvatar = (author) => (
<img
className="item-flex flex-image"
src={author.image_url ? getImageUrl(`images/${author.image_url}`) : 'https://b-ssl.duitang.com/uploads/item/201511/13/20151113110434_kyReJ.jpeg'}
alt=""
/>
);
// 评论信息
const commentInfo = () => (
<p className="item-header">
<span className="item-name">用户名</span>
<span className="item-time">{moment(new Date(), 'YYYYMMDD HHmmss').fromNow()}</span>
<span className="item-close"><Icon type="close" onClick={deleteComment}/></span>
</p>
);
const commentInfo = (id, author, time, can_delete) => {
const _classNames = can_delete ? 'item-close' : 'item-close hide';
return (
<div className="item-header">
<span className="item-name">{author.name || ''}</span>
<span className="item-time">{time || ''}</span>
<span className={_classNames}>
<span className="iconfont icon-shanchu icon_font_size_14" onClick={() => deleteComment(id)}></span>
</span>
</div>
);
};
// 评论内容
const commentCtx = (ctx) => (
<p className="item-ctx">
这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容
</p>
);
const commentCtx = (ctx) => {
let _ctx = null;
try {
_ctx = JSON.parse(ctx);
} catch (e) {
_ctx = ctx;
}
return (
<QuillForEditor
readOnly={true}
value={_ctx}
/>
)};
// 加载更多
const handleOnLoadMore = () => {
if (!arrow) {
// 展开所有
} else {
// 收起
}
const handleOnLoadMore = (len) => {
setShowItemCount(!arrow ? len : 1);
setArrow(!arrow);
};
// 评论追加内容
const commentAppend = () => {
const commentAppend = (children = []) => {
const len = children.length;
const _moreClass = len > showItemCount ? 'comment_item_loadmore show' : 'comment_item_loadmore'
const lastTxt = len - showItemCount;
const renderChild = (children) => {
return children.map((child, i) => {
const {
id, // 评论id
author = {},
time,
content,
can_delete
} = child;
const showOrHide = i < showItemCount ? 'comment_item_show' : 'comment_item_hide';
return (
<li
key={`child_${i}`}
className={showOrHide}
>
<div className="comment_item_area comment_child_item_area">
{commentAvatar(author)}
<div className="item-flex item-desc">
{commentInfo(id, author, time, can_delete)}
{commentCtx(content)}
</div>
</div>
</li>
);
})
}
const _clazz = len > 0 ? 'comment_item_append_list active' : 'comment_item_append_list';
return (
<ul className="comment_item_append_list">
<li className="comment_item_area">
{commentAvatar()}
<div className="item-flex item-desc">
{commentInfo()}
{commentCtx()}
</div>
</li>
<li className="comment_item_area">
{commentAvatar()}
<div className="item-flex item-desc">
{commentInfo()}
{commentCtx()}
</div>
</li>
<li className="comment_item_area">
{commentAvatar()}
<div className="item-flex item-desc">
{commentInfo()}
{commentCtx()}
</div>
</li>
<ul className={_clazz}>
{renderChild(children)}
<li className="comment_item_loadmore" onClick={handleOnLoadMore}>
<p className="loadmore-txt">展开其余23条评论</p>
<li className={_moreClass} onClick={() => handleOnLoadMore(len)}>
<p className="loadmore-txt">展开其余{lastTxt}条评论</p>
<p className="loadmore-icon">
<Icon type={!arrow ? 'down' : 'up'}/>
</p>
@ -109,7 +154,14 @@ function CommentItem ({
);
};
// 点击图标
const handleIconClick = () => {}
const handleShowOrHide = (id, hidden) => {
showOrHideComment && showOrHideComment(id, hidden);
}
// 点赞
const handleClickLick = (id) => {
likeComment && likeComment(id);
}
// 点击评论icon
const handleClickMessage = () => {
@ -122,31 +174,52 @@ function CommentItem ({
}
// 点击保存
const handleClickSubmit = (content) => {
// 保存并关闭
setShowQuill(false);
console.log('获取保存内容', content);
const handleClickSubmit = (id) => {
return (ctx) => {
setShowQuill(false);
submitChildComment && submitChildComment(id, ctx);
}
}
return (
<li className="comment_item_area">
{commentAvatar()}
{commentAvatar(author)}
<div className="item-flex item-desc">
{commentInfo()}
{commentCtx()}
{commentInfo(id, author, time, can_delete)}
{commentCtx(content)}
{commentAppend()}
{commentAppend(children)}
<div className="comment_icon_area">
<CommentIcon className='comment-icon-margin' type="eye" count="100" iconClick={handleIconClick}/>
<CommentIcon
style={{ display: isAdmin ? 'inline-block' : 'none'}}
className='comment-icon-margin'
type={!hidden ? "xianshi" : 'yincang1'}
iconClick={() => handleShowOrHide(id, !hidden ? 1 : 0)}
/>
<CommentIcon
style={{ display: can_delete ? 'inline-block' : 'none'}}
className='comment-icon-margin'
type={'shanchu'}
iconClick={() => deleteComment(id)}
/>
{/* 回复 */}
<CommentIcon
className='comment-icon-margin'
type="message" count="100"
type="huifu1"
count={children.length}
iconClick={handleClickMessage}
/>
{/* 点赞 */}
<CommentIcon/>
<CommentIcon
iconColor={ user_praise ? '#5091FF' : '' }
className='comment-icon-margin'
theme={user_praise ? 'filled' : ''}
type="dianzan"
count={praise_count}
iconClick={() => handleClickLick(id)}
/>
</div>
<div
@ -154,7 +227,7 @@ function CommentItem ({
className="comment_item_quill">
<CommentForm
onCancel={handleClickCancel}
onSubmit={handleClickSubmit}
onSubmit={handleClickSubmit(id)}
/>
</div>
</div>

@ -3,16 +3,52 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 17:34:00
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 11:48:09
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-24 18:08:07
*/
import './index.scss';
import React from 'react';
import CommentItem from './CommentItem';
function CommentList ({}) {
import { Empty } from 'antd';
function CommentList (props) {
const {
isAdmin,
commentLists, // 评论列表
submitChildComment,
submitDeleteComment,
likeComment,
showOrHideComment
} = props;
const {comments = []} = commentLists;
const renderLi = () => {
if (comments.length > 0) {
return comments.map((item, index) => {
return (
<CommentItem
isAdmin={isAdmin}
key={`item_${index}`}
submitChildComment={submitChildComment}
submitDeleteComment={submitDeleteComment}
comment={item}
likeComment={likeComment}
showOrHideComment={showOrHideComment}
/>
);
});
} else {
return (
<div className="empty_comment">
<Empty />
</div>
);
}
}
return (
<ul className="comment_list_wrapper">
<CommentItem />
{renderLi()}
</ul>
);
}

@ -3,18 +3,42 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 17:31:33
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 11:47:39
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-24 18:03:21
*/
import React from 'react';
import CommentForm from './CommentForm';
// import CommentForm from './CommentForm';
import CommentList from './CommentList';
function Comment (props) {
const {
commentLists,
// addComment,
// cancelComment,
isAdmin,
addChildComment,
likeComment,
showOrHideComment,
submitDeleteComment
} = props;
// const handleCancelComment = () => {
// cancelComment && cancelComment();
// };
return (
<React.Fragment>
<CommentForm />
<CommentList />
{/* <CommentForm
onCancel={handleCancelComment}
onSubmit={addComment}
/> */}
<CommentList
isAdmin={isAdmin}
likeComment={likeComment}
showOrHideComment={showOrHideComment}
commentLists={commentLists}
submitChildComment={addChildComment}
submitDeleteComment={submitDeleteComment}
/>
</React.Fragment>
);
}

@ -8,13 +8,32 @@ $ml: 20px;
.comment_list_wrapper{
box-sizing: border-box;
border-top: 1px solid $bdColor;
// border-top: 1px solid $bdColor;
.empty_comment{
display: flex;
height: calc(100vh - 200px);
width: 100%;
justify-content: center;
align-items: center;
}
.comment_item_show{
display: block;
}
.comment_item_hide{
display: none;
}
.comment_item_area{
display: flex;
padding: 20px 0;
box-sizing: border-box;
border-bottom: 1px solid $bdColor;
.comment_child_item_area:hover{
.item-close{
display: inline-block;
}
}
.flex-image{
width: 48px;
height: 48px;
@ -34,15 +53,23 @@ $ml: 20px;
margin-left: $ml;
}
.item-close{
float: right;
display: none;
cursor: pointer;
float: right;
}
.item-close.hide{
display: none;
}
}
.item-ctx{
position: relative;
line-height: $lh22;
font-size: $fz12;
color: #333;
margin-top: 10px;
vertical-align: top;
}
.comment_icon_area{
display: flex;
@ -50,13 +77,16 @@ $ml: 20px;
margin-top: 10px;
.comment-icon-margin{
margin-left: 30px;
margin-left: 20px;
}
.comment-icon-margin-10{
margin-left: 10px;
}
}
.comment_item_quill{
margin-top: 20px;
}
// .comment_item_quill{
// // margin-top: 10px;
// }
}
.comment_icon_count{
cursor: pointer;
@ -71,6 +101,9 @@ $ml: 20px;
margin-left: 10px;
transition: color .3s;
}
.comment_count_none{
margin-left: 0;
}
&:hover{
.comment_icon,
@ -80,11 +113,15 @@ $ml: 20px;
}
}
.comment_item_append_list{
display: none;
position: relative;
background-color: $bgColor;
border-radius: 5px;
padding: 0 15px 10px;
margin: 15px 0;
&.active{
display: block;
}
&::before {
position: absolute;
left: 15px;
@ -98,6 +135,7 @@ $ml: 20px;
}
.comment_item_loadmore{
display: none;
padding-top: 10px;
cursor: pointer;
.loadmore-txt,
@ -106,6 +144,45 @@ $ml: 20px;
text-align: center;
font-size: $fz12;
}
&.show{
display: block;
}
}
}
.icon_font_size_14{
font-size: 14px !important;
}
}
.comment_form_area,
.comment_form_bottom_area{
width: 100%;
}
.comment_form_area{
position: relative;
background: #fff;
// top: 10px;
.ant-form-explain{
padding-left: 0px;
}
.show_input{
margin-top: 10px;
}
}
.comment_form_bottom_area{
position: relative;
background: #fff;
top: 10px;
&.active{
position: absolute;
background: #fff;
left: 0px;
right: 0px;
top: -230px;
padding: 0 20px;
}
}

@ -0,0 +1,80 @@
/*
* @Description: quill delta -> html
* @Author: tangjiang
* @Github:
* @Date: 2019-12-24 08:51:25
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 09:30:11
*/
export const formatDelta = (deltas) => {
let formatted = [];
deltas.forEach(element => {
let text = null;
// 没有图片时
if (!element['insert']['image']) {
text = element['insert']; // 获取插入的内容
// 元素有属性时
if (element['attributes']) {
// 获取所有的key值
const keys = Object.keys(element['attributes']);
keys.forEach(key => {
text = operate(text, key, element['attributes'][key]);
});
} else if (element['insert']['formula']) {
text = element['insert']['formula'];
}
} else {
const image = element['insert']['image'];
const {url, alt} = image;
if (url && (url.startsWith('http') || url.startsWith('https'))) {
text = `
<img
src="${url}"
style="{display: 'inline-block'}"
width="60px"
height="30px"
alt="${alt}"
/>
`;
// text = "<img src="+url+" width='60px' height='30px' onclick='' alt="+alt+"/>";
}
}
formatted.push(text);
});
console.log(formatted);
return formatted.join('');
}
/**
* @param {*} text 文本内容
* @param {*} key 属性key
* @param {*} value 属性key对应的值
*/
export const operate = (text, key, value) => {
let operatedText = null;
debugger;
switch (key) {
case 'bold':
operatedText = `<strong>${text}</strong>`;
break;
case 'italic':
operatedText = `<i>${text}</i>`;
break;
case 'strike':
operatedText = `<s>${text}</s>`;
break;
case 'underline':
operatedText = `<u>${text}</u>`;
break;
case 'link':
operatedText = `<a href="${value}" style="color: #5091ff; text-decoration: underline;" target="bland">${text}</a>`;
break;
default:
operatedText = text;
}
return operatedText;
}

@ -3,8 +3,8 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-12-16 15:50:45
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-17 16:44:48
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-24 09:44:03
*/
import Quill from "quill";
@ -43,8 +43,8 @@ export default class ImageBlot extends BlockEmbed {
alt: node.getAttribute('alt'),
url: node.getAttribute('src'),
onclick: node.onclick,
// width: node.width,
// height: node.height,
width: node.width,
height: node.height,
display: node.getAttribute('display')
};
}

@ -3,8 +3,8 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-12-18 08:49:30
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-20 16:07:37
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 10:22:26
*/
import './index.scss';
import 'quill/dist/quill.core.css'; // 核心样式
@ -32,7 +32,8 @@ function QuillForEditor ({
style = {},
wrapStyle = {},
showUploadImage,
onContentChange
onContentChange,
// getQuillContent
}) {
// toolbar 默认值
const defaultConfig = [
@ -54,8 +55,8 @@ function QuillForEditor ({
// 文本内容变化时
const handleOnChange = content => {
// console.log('编辑器内容====》》》》', content);
onContentChange && onContentChange(content);
// getQuillContent && getQuillContent(quill);
onContentChange && onContentChange(content, quill);
};
const renderOptions = options || defaultConfig;

@ -336,7 +336,7 @@ class CommonWorkDetailIndex extends Component{
}
`}</style>
{this.props.isAdmin()? <Spin spinning={this.state.donwloading} style={{ }}>
<li className="li_line drop_down fr color-blue font-16 mr8 mt20" style={{"padding":"0 20px"}}>
<li className="li_line drop_down fr color-blue font-16 mt20" style={{"padding":"0 20px"}}>
导出<i className="iconfont icon-xiajiantou font-12 ml2"></i>
<ul className="drop_down_menu" style={{"right":"-34px","left":"unset","height":"auto"}}>
<li>

@ -788,7 +788,7 @@ class CommonWorkList extends Component{
{/* value={search} */}
<div className="fr mr5 search-new mr8" style={{marginBottom:'1px'}}>
<div className="fr search-new mr8" style={{marginBottom:'1px'}}>
<Search
placeholder="请输入姓名或学号搜索"
id="subject_search_input"

@ -147,7 +147,7 @@ class TabRightComponents extends Component{
padding-bottom: 8px;
}
`}</style>
{this.props.isAdmin()? <li className="li_line drop_down fr color-blue font-16 mr8 mt20" style={{"padding":"0 20px"}}>
{this.props.isAdmin()? <li className="li_line drop_down fr color-blue font-16 mt20" style={{"padding":"0 20px"}}>
导出<i className="iconfont icon-xiajiantou font-12 ml2"></i>
<ul className="drop_down_menu" style={{"right":"-34px","left":"unset","height":"auto"}}>
<li><a href={exportResultUrl} onClick={(url)=>this.confirmysl(exportResultUrl)} className="color-dark">导出成绩</a></li>

@ -102,11 +102,26 @@ class CoursesHome extends Component{
})
}
getUser=(url,type)=>{
if(this.props.checkIfLogin()===false){
this.props.showLoginDialog()
return
}
if(this.props.checkIfProfileCompleted()===false){
this.props.showProfileCompleteDialog()
return
}
if(url !== undefined || url!==""){
this.props.history.push(url);
}
}
render() {
let { order,search,page,coursesHomelist }=this.state;
//console.log(this.props)
return (
<div>
{this.state.updata===undefined?"":<UpgradeModals
@ -144,6 +159,7 @@ class CoursesHome extends Component{
onClick={ () => this.changeStatus("created_at")}>最新</a>
<a className={ order == "visits" ? "fl mr20 font-16 bestChoose active" : "fl mr20 font-16 bestChoose"}
onClick={ () => this.changeStatus("visits")}>最热</a>
{this.props.user&&this.props.user.user_identity==="学生"?"":<span className={ "fr font-16 bestChoose color-blue" } onClick={(url)=>this.getUser("/courses/new")}>+新建翻转课堂</span>}
{/*<div className="fr mr5 search-new">*/}
{/*/!* <Search*/}

@ -26,14 +26,21 @@ class NewShixunModel extends Component{
componentDidMount() {
let{page,type,keyword,order,diff,limit,status,sort}=this.state;
if(this.props.type==='shixuns'){
this.getdatalist(page,type,status,keyword,order,diff,limit)
this.getdatalist(page,type,status,keyword,order,diff,limit,undefined,sort);
}else{
this.getdatalist(page,type,undefined,keyword,order,undefined,limit,undefined,sort);
}
}
getdatalist=(page,type,newstatus,keyword,order,diff,limit,pagetype,sort)=>{
getdatalist=(page,type,newstatus,keyword,order,diff,limit,pagetype,sorts)=>{
let newsort=sorts;
if(this.props.type==="shixuns"&&type==="mine"){
if(this.props&&this.props.user.course_name===undefined){
newsort="created_at";
}else{
newsort="publish_time";
}
}
this.setState({
isspinning:true
})
@ -45,14 +52,14 @@ class NewShixunModel extends Component{
url="/subject_lists.json";
}
axios.get(url,{params:{
page,
type,
status,
keyword,
order,
diff,
limit,
sort
page:page,
type:type,
status:status,
keyword:keyword,
order:order,
diff:diff,
limit:limit,
sort:newsort
}}).then((response) => {
if(response.data){
if(pagetype===undefined){
@ -368,6 +375,7 @@ class NewShixunModel extends Component{
// let {visible,patheditarry}=this.props;
// console.log(Grouplist)
// console.log(allGrouplist)
const statusmenus=(
<Menu className="menus">
<Menu.Item>
@ -425,7 +433,6 @@ class NewShixunModel extends Component{
);
console.log(shixun_list)
return(
<div>

@ -1338,7 +1338,7 @@ samp {
width:237px!important;
height: 30px;
margin-bottom: 30px;
margin-right: 35px;
/*margin-right: 35px;*/
}
.search-new-input {
padding-left: 16px;

@ -178,16 +178,9 @@ class Exercisesetting extends Component{
}
if(result.data.exercise.unified_setting == true && moment(result.data.exercise.end_time) <= moment()){
// if(this.props.isSuperAdmin()===true){
// this.setState({
// end_timetype:false
// })
// }else{
this.setState({
end_timetype:true
})
// }
}
let group=result.data.published_course_groups;
@ -236,10 +229,10 @@ class Exercisesetting extends Component{
//提交form表单
handleSubmit = (e) => {
e.preventDefault();
if(this.props&&this.props.Commonheadofthetestpaper.exercise_status){
console.log("190");
console.log(this.props.Commonheadofthetestpaper.exercise_status);
}
// if(this.props&&this.props.Commonheadofthetestpaper.exercise_status){
// console.log("190");
// console.log(this.props.Commonheadofthetestpaper.exercise_status);
// }
this.props.form.validateFieldsAndScroll((err, values) => {
if(!err){
@ -327,7 +320,7 @@ class Exercisesetting extends Component{
}
}
if(this.state.end_timetype === false){
if(this.state.end_timetype === false||this.props.isAdmin()==true){
if(moment(end_time,dataformat) <= moment(publish_time,dataformat)){
this.setState({
unit_e_tip:"截止时间不能小于发布时间"
@ -525,7 +518,7 @@ class Exercisesetting extends Component{
end_time:null
})
}else{
if(moment(date,"YYYY-MM-DD HH:mm") <= moment()){
if(dateString<=moment().format('YYYY-MM-DD HH:mm')){
this.setState({
unit_e_tip:"截止时间不能早于当前时间",
e_flag:true
@ -608,9 +601,9 @@ class Exercisesetting extends Component{
};
// console.log(flagPageEdit===true?polls&&polls.exercise_status===1?3:2:1)
console.log("asdasdasda");
console.log(this.props);
console.log(this.props.Commonheadofthetestpaper);
// console.log("asdasdasda");
// console.log(this.props);
// console.log(this.props.Commonheadofthetestpaper);
return(
<div>
<Modals
@ -681,6 +674,7 @@ class Exercisesetting extends Component{
<div className="clearfix">
<span className="mr15 fl mt10 font-16">截止时间</span>
<div className="fl">
<Tooltip placement="bottom" title={end_timetype===true ? this.props.isAdmin()?"":"截止时间已过,不能再修改":""}>
<DatePicker
dropdownClassName="hideDisable"
placeholder="请选择截止时间"
@ -694,9 +688,10 @@ class Exercisesetting extends Component{
disabledDate={disabledDate}
onChange={this.onChangeTimeEnd}
value={end_time && moment(end_time,"YYYY-MM-DD HH:mm")}
disabled={ end_timetype===true?true:!flagPageEdit}
disabled={ end_timetype===true?this.props.isAdmin()?!flagPageEdit:true:!flagPageEdit}
>
</DatePicker>
</Tooltip>
<p className="color-red lineh-25 clearfix" style={{height:"25px"}}>
{
unit_e_tip && unit_e_tip != "" ? <span className="fl">{ unit_e_tip }</span>:""

@ -2882,7 +2882,7 @@ class Studentshavecompletedthelist extends Component {
})
}
</CheckboxGroup>
<div className="fr mr5 search-new" style={{marginBottom: '1px'}}>
<div className="fr search-new" style={{marginBottom: '1px'}}>
<Search
placeholder="请输入姓名或学号搜索"
id="subject_search_input"
@ -2914,7 +2914,7 @@ class Studentshavecompletedthelist extends Component {
})
}
</CheckboxGroup>
<div className="fr mr5 search-new" style={{marginBottom: '1px'}}>
<div className="fr search-new" style={{marginBottom: '1px'}}>
<Search
placeholder="请输入姓名或学号搜索"
id="subject_search_input"

@ -1158,7 +1158,7 @@ class GraduationTaskssettinglist extends Component{
)
})}
</CheckboxGroup>
<div className="fr mr5 search-new" style={{marginBottom:'1px',marginRight:"0px"}}>
<div className="fr search-new" style={{marginBottom:'1px',marginRight:"0px"}}>
<Search
placeholder="请输入姓名或学号搜索"
id="subject_search_input"
@ -1350,7 +1350,7 @@ class GraduationTaskssettinglist extends Component{
})}
</CheckboxGroup>
<div className="fr mr5 search-new" style={{marginBottom:'1px'}}>
<div className="fr search-new" style={{marginBottom:'1px'}}>
<Search
placeholder="请输入姓名或学号搜索"
id="subject_search_input"

@ -666,7 +666,7 @@ class GraduationTasks extends Component{
<React.Fragment>
{/*{this.props.isAdmin() ?<WordsBtn style="blue" className="mr30" onClick={() => this.addDir()}>题库选用</WordsBtn>:""}*/}
{/*{this.props.isAdmin() ?<a href={"/api/graduation_tasks/"+category_id+"/tasks_list.xls"} className={"fr color-blue font-16"}>导出成绩</a> :""}*/}
{this.props.isAdmin() ? <WordsBtn style="blue" className="mr10 fr font-16">
{this.props.isAdmin() ? <WordsBtn style="blue" className=" fr font-16">
<Link to={"/courses/" + coursesId + "/graduation_tasks/"+category_id+"/new"}>
<span className={"color-blue font-16"}>新建</span>
</Link>

@ -33,6 +33,11 @@ function disabledDateTime() {
// disabledSeconds: () => [55, 56],
};
}
function disabledDate(current) {
return current && current < moment().endOf('day').subtract(1, 'days');
}
const dataformat="YYYY-MM-DD HH:mm";
class PollDetailTabForth extends Component{
@ -597,9 +602,10 @@ class PollDetailTabForth extends Component{
width={"240px"}
format="YYYY-MM-DD HH:mm"
disabledTime={disabledDateTime}
disabledDate={disabledDate}
onChange={this.onChangeTimeEnd}
value={ end_time && moment(end_time,dataformat) }
disabled={un_change_end == true ? true : !flagPageEdit }
disabled={un_change_end == true ?true : !flagPageEdit }
>
</DatePicker>
</span>
@ -619,6 +625,7 @@ class PollDetailTabForth extends Component{
{...this.state}
ref="pollDetailTabForthRules"
rules={rules}
type={"polls"}
course_group={course_group}
flagPageEdit={flagPageEdit}
rulesCheckInfo={(info)=>this.rulesCheckInfo(info)}

@ -447,7 +447,7 @@ class PollDetailTabForthRules extends Component{
</p>
</div>
<div className="fl mr20 yskspickersy">
<Tooltip placement="bottom" title={rule.e_timeflag ? this.props.isAdmin()?"截止时间已过,不能再修改":"":""}>
<Tooltip placement="bottom" title={rule.e_timeflag ? this.props.isAdmin()?"":"截止时间已过,不能再修改":""}>
<span>
<DatePicker
showToday={false}
@ -461,7 +461,11 @@ class PollDetailTabForthRules extends Component{
format="YYYY-MM-DD HH:mm"
disabledTime={disabledDateTime}
disabledDate={disabledDate}
disabled={rule.e_timeflag === undefined ? rule.publish_time === null ? false : moment(rule.end_time, dataformat) <= moment() ? true : !flagPageEdit : rule.e_timeflag == true ? true : !flagPageEdit}
disabled={
this.props.type==="Exercise"?
rule.e_timeflag === undefined ? rule.publish_time === null ? false : moment(rule.end_time, dataformat) <= moment() ?this.props.isAdmin()?!flagPageEdit: true : !flagPageEdit : rule.e_timeflag == true ? this.props.isAdmin()?!flagPageEdit :true : !flagPageEdit:
rule.e_timeflag === undefined ? rule.publish_time === null ? false : moment(rule.end_time, dataformat) <= moment() ? true : !flagPageEdit : rule.e_timeflag == true ? true : !flagPageEdit
}
style={{"height":"42px"}}
></DatePicker>
</span>

@ -1,5 +1,5 @@
.editormd-html-preview, .editormd-preview-container {
width: 95% !important;
width: 100% !important;
}
.Finish_button{
height: 30px;

@ -1667,7 +1667,7 @@ class Listofworksstudentone extends Component {
],
yslpros: false,
datajs: [],
homework_status: [],
homework_status: undefined,
}
}
@ -3597,7 +3597,14 @@ class Listofworksstudentone extends Component {
starttimesend={this.state.starttimesend}
typs={this.state.typs}
/> : ""}
{
{homework_status===undefined?
<div className={"educontent "}>
<div className="edu-back-white">
<div className="edu-tab-con-box clearfix edu-txt-center" style={{ width:"100%",height:"200px" }}>
<Spin style={{ width:"100%","line-height":"200px" }}></Spin>
</div>
</div>
</div>:
homework_status && homework_status.length === 0 ?
<div className="edu-back-white">
<NoneData></NoneData>
@ -3609,11 +3616,7 @@ class Listofworksstudentone extends Component {
</div>
:
<div className={"educontent "}>
<div className="edu-back-white">
<style>
{`
.startbox{
@ -3679,7 +3682,7 @@ class Listofworksstudentone extends Component {
</li>
<li className="clearfix mt10">
<div className="fr mr5 search-newysl" style={{marginBottom: '1px'}}>
<div className="fr search-newysl" style={{marginBottom: '1px'}}>
{/*{course_is_end===true?"":<span>*/}
{/*{teacherdata&&teacherdata.update_score===true&&computeTimetype===true?*/}
{/* (this.props.isNotMember()===false?<div className={"computeTime font-16"} onClick={this.setComputeTimet}>*/}

@ -291,7 +291,7 @@ class ShixunHomeworkPage extends Component {
`}</style>
{this.props.isAdmin() ?
<li className="li_line drop_down fr color-blue font-16 mr8 mt20" style={{"padding": "0 20px"}}>
<li className="li_line drop_down fr color-blue font-16 mt20" style={{"padding": "0 20px"}}>
导出<i className="iconfont icon-xiajiantou font-12 ml2"></i>
<ul className="drop_down_menu" style={{"right": "-0px", "left": "unset", "height": "auto"}}>
{/*<li><a*/}

@ -898,7 +898,7 @@ class ShixunStudentWork extends Component {
</span>:"":""}
{/*请输入姓名或学号搜索*/}
<div className="fr mr5 search-new" style={{marginBottom: '1px'}}>
<div className="fr search-new" style={{marginBottom: '1px'}}>
<Search
placeholder="请输入姓名或学号搜索"
id="subject_search_input"

@ -3,8 +3,8 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 16:02:36
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-20 14:37:39
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 15:17:28
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
@ -23,7 +23,7 @@ const ControlSetting = (props) => {
submitLoading,
identifier,
excuteState,
showOrHideControl,
// showOrHideControl,
commitTestRecordDetail,
changeLoadingState,
changeSubmitLoadingStatus,

@ -31,6 +31,14 @@
opacity: 0;
}
}
.ant-tabs-bar{
padding: 0;
}
.ant-tabs-nav .ant-tabs-tab{
padding: 12px 0px;
}
}
@ -52,7 +60,7 @@
z-index: 20;
height: 56px;
padding-right: 20px;
padding-left: 10px;
padding-left: 5px;
background: rgba(18,28,36,1);
// background:rgba(48,48,48,1);
}

@ -3,8 +3,8 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-12-03 15:20:55
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-03 20:36:24
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-25 11:41:42
*/
import './index.scss';
import React from 'react';
@ -46,6 +46,7 @@ function ErrorResult (props) {
case 3: // 创建pod失败
result = (
<div className={'error_result_wrap'}>
<p>系统繁忙请稍后重试</p>
</div>
);
break;

@ -3,8 +3,8 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-11-28 08:44:54
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 10:44:16
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 08:51:21
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
@ -82,9 +82,16 @@ function ExecResult (props) {
} else if (state === 4){
return (
<p className={'result_info_style'}>
{/* 系统繁忙,请稍后重试 */}
{error_msg}
</p>
)
} else if (state === 3) {
return (
<p className={'result_info_style'}>
系统繁忙请稍后重试
</p>
)
} else if (state === -1) {
return (
<React.Fragment>

@ -41,7 +41,7 @@
}
}
.flex_l{
padding: 0 10px 0 20px;
padding: 0 10px 0 10px;
color: #fff;
}
.flex_r{

@ -3,8 +3,8 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 15:02:52
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-20 20:07:11
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 15:19:34
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
@ -155,7 +155,7 @@ function MyMonacoEditor (props, ref) {
onClick={handleUpdateNotice}
>
{/* <Icon type="bell" /> */}
<MyIcon type="iconxiaoxi1" />
<MyIcon type="iconxiaoxi1" style={{fontSize: '18px'}}/>
</Badge>
</Tooltip>
<Tooltip
@ -166,7 +166,7 @@ function MyMonacoEditor (props, ref) {
className="flex_normal"
onClick={handleRestoreCode}
type="iconzaicizairu"
style={{ display: identifier ? 'inline-block' : 'none'}}
style={{ display: identifier ? 'inline-block' : 'none', fontSize: '18px'}}
/>
{/* <span onClick={handleRestoreCode} className="flex_normal" style={{ display: identifier ? 'inline-block' : 'none'}}>{renderRestore}</span> */}
</Tooltip>
@ -174,7 +174,7 @@ function MyMonacoEditor (props, ref) {
placement="bottom"
title="设置"
>
<MyIcon className='code-icon' type="iconshezhi" onClick={handleShowDrawer}/>
<MyIcon className='code-icon' type="iconshezhi" onClick={handleShowDrawer} style={{fontSize: '18px'}}/>
</Tooltip>
</div>
<MonacoEditor

@ -26,6 +26,18 @@
color: #666;
}
}
.margin,
.margin-view-overlays,
.current-line{
width: 40px !important;
}
.monaco-editor .margin-view-overlays .line-numbers{
text-align: center;
}
.monaco-scrollable-element{
left: 40px !important;
}
}
.setting_drawer{

@ -3,12 +3,12 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 10:58:37
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 14:22:38
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-25 10:02:03
*/
import './index.scss';
import React from 'react';
import { Icon } from 'antd';
// import { Icon } from 'antd';
const numberal = require('numeral');
const TextNumber = (props) => {
@ -19,8 +19,17 @@ const TextNumber = (props) => {
* type: 内容 文字或图标
* onIconClick: 点击图标时的回调函数
*/
const { text, number, position = 'horizontal', type = 'label', onIconClick} = props;
const {
text,
number,
position = 'horizontal',
type = 'label',
onIconClick,
className,
theme = 'outlined'
} = props;
// console.log('style=====>>>>>>', style);
const handleIconClick = () => {
onIconClick && onIconClick();
}
@ -35,11 +44,21 @@ const TextNumber = (props) => {
}
return '';
}
const renderCtx = () => {
const renderCtx = (className, theme) => {
if (type === 'icon') { // 图标加文字时
const _className = `text_number_area text_icon_numb flex_${position} ${className}`;
const _classIcon = `iconfont icon-${text} numb_icon`;
return (
<div className={`text_number_area text_icon_numb flex_${position}`}>
<Icon onClick={handleIconClick} type={text} className={'numb_icon'}></Icon>
<div className={_className}>
{/* <Icon
theme={theme}
type={text}
className={'numb_icon'}
></Icon> */}
<span
className={_classIcon}
onClick={handleIconClick}>
</span>
{renderNumb()}
</div>
)
@ -54,7 +73,7 @@ const TextNumber = (props) => {
}
return (
<React.Fragment>
{renderCtx()}
{renderCtx(className, theme)}
</React.Fragment>
);
}

@ -77,7 +77,12 @@
}
}
.oj_item_name{
flex-wrap: wrap;
color: #459be5;
cursor: pointer;
max-width: 510px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
}

@ -9,7 +9,7 @@ import './index.scss';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import SplitPane from 'react-split-pane';// import { Form } from 'antd';
import { Button, Modal } from 'antd';
import { Button } from 'antd';
import LeftPane from './leftpane';
import RightPane from './rightpane';
import { withRouter } from 'react-router';

@ -3,8 +3,8 @@
* @Author: tangjiang
* @Github:
* @Date: 2019-11-21 09:19:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-20 17:32:10
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 15:33:01
*/
import './index.scss';
import React from 'react';
@ -17,7 +17,7 @@ const { TextArea } = Input;
const FormItem = Form.Item;
const AddTestDemo = (props) => {
const {
key,
// key,
// onSubmitTest,
onDeleteTest,
testCase,

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

Loading…
Cancel
Save