Merge branch 'dev_aliyun' into develop

newyslclassrooms
cxt 5 years ago
commit 3923cf64c3

@ -330,7 +330,7 @@ class ApplicationController < ActionController::Base
end
if !User.current.logged? && Rails.env.development?
User.current = User.find 1
User.current = User.find 3117
end

@ -155,8 +155,14 @@ class ChallengesController < ApplicationController
def index
uid_logger("identifier: #{params}")
@challenges = @shixun.challenges.fields_for_list
base_columns = "challenges.id, challenges.subject, challenges.st, challenges.score, challenges.position,
challenges.shixun_id, games.identifier, games.status"
join_sql = "LEFT JOIN games ON games.challenge_id = challenges.id AND games.user_id = #{current_user.id}"
# 下面2个参数是为了解决列表获取通关人数与正在游玩人数的问题
@pass_games_map = @shixun.challenges.joins(:games).where(games: {status:2}).group(:challenge_id).reorder(nil).count
@play_games_map = @shixun.challenges.joins(:games).where(games: {status:[0,1]}).group(:challenge_id).reorder(nil).count
@challenges = @shixun.challenges.joins(join_sql).select(base_columns)
@editable = @shixun.status == 0 # before_action有判断权限如果没发布则肯定是管理人员
@user = current_user

@ -2,9 +2,13 @@ class CourseModulesController < ApplicationController
before_action :require_login, :check_auth
before_action :set_module, except: [:unhidden_modules]
before_action :find_course, only: [:unhidden_modules]
before_action :teacher_or_admin_allowed, except: [:add_second_category]
before_action :teacher_or_admin_allowed, except: [:show, :add_second_category]
before_action :teacher_allowed, only: [:add_second_category]
def show
end
# 模块置顶
def sticky_module
# position为1则不做处理否则该模块的position置为1position小于当前模块的position加1

@ -0,0 +1,35 @@
class CourseVideosController < ApplicationController
before_action :require_login
before_action :validate_params
before_action :find_course, only: [:create]
before_action :find_video, only: [:update]
before_action :teacher_allowed
def create
title = params[:name].strip
link = params[:link].strip
course_second_category_id = params[:category_id] || 0
@course.course_videos.create!(title: title, link: link, is_link: 1, user_id: current_user.id, course_second_category_id: course_second_category_id)
render_ok
end
def update
title = params[:name].strip
link = params[:link].strip
@video.update!(title: title, link: link)
render_ok
end
private
def validate_params
tip_exception("视频名称不能为空") if params[:name].blank?
tip_exception("链接地址不能为空") if params[:link].blank?
end
def find_video
@video = CourseVideo.find params[:id]
@course = @video.course
end
end

@ -30,14 +30,14 @@ class CoursesController < ApplicationController
:informs, :update_informs, :online_learning, :update_task_position, :tasks_list,
:join_excellent_course, :export_couser_info, :export_member_act_score, :new_informs,
:delete_informs, :change_member_role, :course_groups, :join_course_group, :statistics,
:work_score, :act_score, :calculate_all_shixun_scores]
:work_score, :act_score, :calculate_all_shixun_scores, :move_to_category]
before_action :user_course_identity, except: [:join_excellent_course, :index, :create, :new, :apply_to_join_course,
:search_course_list, :get_historical_course_students, :mine, :search_slim, :board_list]
before_action :teacher_allowed, only: [:update, :destroy, :settings, :search_teacher_candidate,
:transfer_to_course_group, :delete_from_course, :export_member_scores_excel,
:search_users, :add_students_by_search, :get_historical_courses, :add_teacher_popup,
:add_teacher, :export_couser_info, :export_member_act_score,
:update_informs, :new_informs, :delete_informs, :switch_to_student]
:update_informs, :new_informs, :delete_informs, :switch_to_student, :move_to_category]
before_action :admin_allowed, only: [:set_invite_code_halt, :set_public_or_private, :change_course_admin,
:set_course_group, :create_group_by_importing_file,
:update_task_position, :tasks_list]
@ -102,22 +102,50 @@ class CoursesController < ApplicationController
end
def course_videos
logger.info("########[#{@course}")
videos = @course.videos
videos = @course.course_videos
@video_module = @course.course_modules.find_by(module_type: "video")
if params[:category_id].present? && params[:category_id].to_i != 0
@category = @video_module&.course_second_categories.find_by(id: params[:category_id])
tip_exception("子目录id有误") if !@category.present?
videos = videos.where(course_second_category_id: params[:category_id].to_i)
end
videos = custom_sort(videos, params[:sort_by], params[:sort_direction])
@count = videos.count
@videos = paginate videos.includes(user: :user_extension)
@videos = paginate videos.includes(video: [user: :user_extension], user: :user_extension)
end
def delete_course_video
video = Video.find_by(id: params[:video_id])
tip_exception(404, "找不到资源") if video.blank?
tip_exception(403, "...") unless (video.user_id == current_user.id || current_user.admin_or_business?)
video.destroy!
AliyunVod::Service.delete_video([video.uuid]) rescue nil
if params[:is_link]
video = @course.course_videos.find_by!(id: params[:video_id])
tip_exception(403, "...") unless (video.user_id == current_user.id || current_user.admin_or_business?)
video.destroy!
else
video = Video.find_by(id: params[:video_id])
tip_exception(404, "找不到资源") if video.blank?
tip_exception(403, "...") unless (video.user_id == current_user.id || current_user.admin_or_business?)
video.destroy!
AliyunVod::Service.delete_video([video.uuid]) rescue nil
end
render_ok
end
# 视频移动到目录
def move_to_category
tip_exception("请选择要移动的目录") if params[:new_category_id].blank?
category = @course.course_second_categories.find_by(id: params[:new_category_id])
if params[:new_category_id].to_i == 0 || category.present?
videos = @course.course_videos.where(video_id: params[:video_ids]).or(@course.course_videos.where(id: params[:video_ids]))
videos.update_all(course_second_category_id: params[:new_category_id])
normal_status(0, "操作成功")
else
normal_status(-1, "目录不存在")
end
end
def visits_plus_one
new_visits = @course.visits + 1
@course.update_visits(new_visits)
@ -1259,7 +1287,7 @@ class CoursesController < ApplicationController
@is_teacher = @user_course_identity < Course::ASSISTANT_PROFESSOR
@course_modules = @course.course_modules.where(hidden: 0)
@hidden_modules = @course.course_modules.where(hidden: 1)
@second_category_type = ["shixun_homework", "graduation", "attachment", "board", "course_group"]
@second_category_type = ["shixun_homework", "graduation", "attachment", "board", "course_group", "video"]
end
def board_list

@ -2,13 +2,13 @@ class FilesController < ApplicationController
include MessagesHelper
before_action :require_login, :check_auth, except: %i[index]
before_action :find_course, except: %i[public_with_course_and_project mine_with_course_and_project]
before_action :find_course, except: %i[public_with_course_and_project mine_with_course_and_project update_visits]
before_action :find_ids, only: %i[bulk_delete bulk_send bulk_move bulk_public bulk_publish]
before_action :file_validate_sort_type, only: :index
before_action :validate_send_message_to_course_params, only: :bulk_send
before_action :set_pagination, only: %i[index public_with_course_and_project mine_with_course_and_project]
before_action :validate_upload_params, only: %i[upload import]
before_action :find_file, only: %i[show setting update]
before_action :validate_upload_params, only: %i[import]
before_action :find_file, only: %i[show setting update update_visits]
before_action :publish_params, only: %i[upload import update]
SORT_TYPE = %w[created_on downloads quotes]
@ -163,6 +163,7 @@ class FilesController < ApplicationController
# 上传资源
def upload
find_course_second_category_id
attachment_ids = params[:attachment_ids]
course_second_category_id = params[:course_second_category_id] || 0 # 0: 为主目录, 其他为次目录id
# is_unified_setting = params.has_key?(:is_unified_setting) ? params[:is_unified_setting] : true
@ -170,25 +171,48 @@ class FilesController < ApplicationController
# course_group_publish_times = params[:course_group_publish_times] || []
begin
attachment_ids.each do |attchment_id|
attachment = Attachment.find_by_id attchment_id
unless attachment.nil?
attachment.container = @course
attachment.course_second_category_id = course_second_category_id
attachment.description = params[:description]
attachment.is_public = params[:is_public] && @course.is_public == 1 ? 1 : 0
attachment.is_publish = @atta_is_publish
attachment.delay_publish = @atta_delay_publish
attachment.publish_time = @atta_publish_time
attachment.unified_setting = @unified_setting
if @unified_setting == 0
attachment_group_setting attachment, params[:group_settings]
if attachment_ids.present?
attachment_ids.each do |attchment_id|
attachment = Attachment.find_by_id attchment_id
unless attachment.nil?
attachment.container = @course
attachment.course_second_category_id = course_second_category_id
attachment.description = params[:description]
attachment.is_public = params[:is_public] && @course.is_public == 1 ? 1 : 0
attachment.is_publish = @atta_is_publish
attachment.delay_publish = @atta_delay_publish
attachment.publish_time = @atta_publish_time
attachment.unified_setting = @unified_setting
if @unified_setting == 0
attachment_group_setting attachment, params[:group_settings]
end
# attachment.set_publish_time(publish_time) if is_unified_setting
# attachment.set_course_group_publish_time(@course, course_group_publish_times) if @course.course_groups.size > 0 && !is_unified_setting && publish_time.blank?
attachment.save!
end
# attachment.set_publish_time(publish_time) if is_unified_setting
# attachment.set_course_group_publish_time(@course, course_group_publish_times) if @course.course_groups.size > 0 && !is_unified_setting && publish_time.blank?
attachment.save!
end
else
tip_exception("资源名称不能为空") if params[:name].blank?
tip_exception("资源名称不能超过60个字符") if params[:name].strip.length > 60
tip_exception("链接地址不能为空") if params[:link].blank?
attachment = Attachment.new
attachment.container = @course
attachment.course_second_category_id = course_second_category_id
attachment.author_id = current_user.id
attachment.filename = params[:name].strip
attachment.link = params[:link].strip
attachment.description = params[:description]
attachment.is_public = params[:is_public] && @course.is_public == 1 ? 1 : 0
attachment.is_publish = @atta_is_publish
attachment.delay_publish = @atta_delay_publish
attachment.publish_time = @atta_publish_time
attachment.unified_setting = @unified_setting
if @unified_setting == 0
attachment_group_setting attachment, params[:group_settings]
end
attachment.save!
end
rescue Exception => e
uid_logger_error(e.message)
tip_exception(e.message)
@ -265,6 +289,11 @@ class FilesController < ApplicationController
@old_attachment.save!
@new_attachment.delete
end
if params[:name].present? && params[:link].present?
tip_exception("资源名称不能超过60个字符") if params[:name].strip.length > 60
@old_attachment.filename = params[:name].strip
@old_attachment.link = params[:link].strip
end
@old_attachment.is_public = is_public == true && @course.is_public == 1 ? 1 : 0
@old_attachment.is_publish = @atta_is_publish
@old_attachment.delay_publish = @atta_delay_publish
@ -326,6 +355,11 @@ class FilesController < ApplicationController
end
end
def update_visits
@file.increment!(:downloads)
render_ok
end
private
def find_file
@file = Attachment.find params[:id]

@ -6,7 +6,7 @@ class HomeworkCommonsController < ApplicationController
before_action :require_login, :check_auth, except: [:index, :choose_category]
before_action :find_course, only: [:index, :create, :new, :shixuns, :subjects, :create_shixun_homework, :publish_homework,
:end_homework, :set_public, :choose_category, :move_to_category, :choose_category,
:end_homework, :set_public, :move_to_category, :choose_category,
:create_subject_homework, :multi_destroy, :add_to_homework_bank]
before_action :find_homework, only: [:edit, :show, :update, :group_list, :homework_code_repeat, :code_review_results,
:code_review_detail, :show_comment, :settings, :works_list, :update_settings,

@ -182,7 +182,7 @@ class ShixunsController < ApplicationController
select m.user_id, u.login, u.lastname, m.updated_at,
(select sum(cost_time) from games g where g.myshixun_id = m.id) as time,
(select sum(final_score) from games g where g.myshixun_id = m.id) as score
from (users u left join myshixuns m on m.user_id = u.id) where m.shixun_id = #{@shixun.id} and m.status = 1
from (users u left join myshixuns m on m.user_id = u.id) where u.is_test =0 and m.shixun_id = #{@shixun.id} and m.status = 1
order by score desc, time asc limit 10
"
@myshixuns = Myshixun.find_by_sql(sql)

@ -1,6 +1,6 @@
class Users::VideosController < Users::BaseController
before_action :private_user_resources!, :check_account
before_action :require_teacher!
before_action :require_teacher!, except: [:destroy]
before_action :require_auth_teacher!, except: [:index, :review]
helper_method :current_video
@ -53,6 +53,19 @@ class Users::VideosController < Users::BaseController
render_error(ex.message)
end
def destroy
video = Video.find_by(id: params[:id])
return render_forbidden unless video.user_id == current_user.id || current_user.admin_or_business?
return render_not_found if video.blank?
return render_error('该状态下不能删除视频') unless video.published?
video.destroy!
AliyunVod::Service.delete_video([video.uuid]) rescue nil
render_ok
end
private
def current_video
@ -72,6 +85,6 @@ class Users::VideosController < Users::BaseController
end
def batch_publish_params
params.permit(videos: %i[video_id title course_id])
params.permit(videos: %i[video_id title course_id category_id])
end
end

@ -42,6 +42,7 @@ class Weapps::AttendancesController < ApplicationController
end
def student_attendances
tip_exception(403) if @user_course_identity > Course::STUDENT
# tip_exception("学生身份的签到列表") if @user_course_identity != Course::STUDENT
member = @course.students.find_by(user_id: current_user.id)
if member.present?
@ -58,12 +59,18 @@ class Weapps::AttendancesController < ApplicationController
@history_attendances = @course.course_attendances.where(id: history_attendance_ids.uniq).
where("attendance_date < '#{current_date}' or (attendance_date = '#{current_date}' and end_time < '#{current_end_time}')").order("id desc")
@current_attendance = @course.course_attendances.where(id: all_attendance_ids.uniq).
@current_attendances = @course.course_attendances.where(id: all_attendance_ids.uniq).
where("attendance_date = '#{current_date}' and start_time <= '#{current_end_time}' and end_time > '#{current_end_time}'")
@history_count = @history_attendances.size
# 当前签到如果存在快捷签到,则直接签到(不在这里处理)
# quick_attendances = @current_attendances.where(mode: "QUICK")
# if quick_attendances.present?
# student_direct_attendance quick_attendances, member
# end
student_attendance_ids = @history_attendances.pluck(:id)
student_attendance_ids += @current_attendance.present? ? @current_attendance.pluck(:id) : []
student_attendance_ids += @current_attendances.present? ? @current_attendances.pluck(:id) : []
if student_attendance_ids.uniq.blank?
@normal_count = 0
@ -141,4 +148,16 @@ class Weapps::AttendancesController < ApplicationController
def edit_auth
tip_exception(403, "") unless @user_course_identity < Course::PROFESSOR || @attendance.user_id == current_user.id
end
def student_direct_attendance quick_attendances, member
quick_attendances.each do |attendance|
current_attendance = attendance.course_member_attendances.find_by(user_id: member.user_id)
if current_attendance.present?
current_attendance.update!(attendance_status: "NORMAL", attendance_mode: "QUICK")
else
attendance.course_member_attendances.create!(course_member_id: member.id, user_id: member.user_id, course_id: attendance.course_id,
course_group_id: member.course_group_id, attendance_status: "NORMAL", attendance_mode: "QUICK")
end
end
end
end

@ -28,19 +28,24 @@ class Weapps::CourseMemberAttendancesController < ApplicationController
end
def create
tip_exception("签到码不能为空") if params[:code].blank?
tip_exception("attendance_mode参数不对") unless ["NUMBER", "QRCODE"].include?(params[:attendance_mode])
tip_exception("签到码不能为空") if params[:attendance_mode] != "QUICK" && params[:code].blank?
tip_exception("attendance_mode参数不对") unless ["NUMBER", "QRCODE", "QUICK"].include?(params[:attendance_mode])
attendance = CourseAttendance.find_by(attendance_code: params[:code])
tip_exception("签到码输入有误") if attendance.blank? || attendance.course.blank?
if params[:attendance_mode] == "QUICK"
attendance = CourseAttendance.find_by(id: params[:attendance_id])
else
attendance = CourseAttendance.find_by(attendance_code: params[:code])
end
tip_exception("该签到不存在") if attendance.blank? || attendance.course.blank?
member = attendance.course.students.find_by(user_id: current_user.id)
tip_exception("签到码输入有误") if member.blank?
tip_exception("该签到不存在") if member.blank?
tip_exception("不在签到时间内") unless attendance.current_attendance?
tip_exception("只支持数字签到") if attendance.mode != "ALL" && attendance.mode == "NUMBER" && params[:attendance_mode] == "QRCODE"
tip_exception("只支持二维码签到") if attendance.mode != "ALL" && attendance.mode == "QRCODE" && params[:attendance_mode] == "NUMBER"
tip_exception("只支持数字签到") if attendance.mode != "ALL" && attendance.mode == "NUMBER" && params[:attendance_mode] != "NUMBER"
tip_exception("只支持二维码签到") if attendance.mode != "ALL" && attendance.mode == "QRCODE" && params[:attendance_mode] != "QRCODE"
tip_exception("只支持快捷签到") if attendance.mode == "QUICK" && params[:attendance_mode] != "QUICK"
current_attendance = attendance.course_member_attendances.find_by(user_id: current_user.id)
if current_attendance.present?

@ -87,16 +87,18 @@ module CoursesHelper
# 子目录对应的url
def category_url category, course
case category.category_type
when "shixun_homework"
"/courses/#{course.id}/shixun_homework/#{category.id}"
when "graduation"
if category.name == "毕设选题"
"/courses/#{course.id}/graduation_topics/#{category.course_module_id}"
else
"/courses/#{course.id}/graduation_tasks/#{category.course_module_id}"
end
when "attachment"
"/courses/#{course.id}/file/#{category.id}"
when "shixun_homework"
"/courses/#{course.id}/shixun_homework/#{category.id}"
when "graduation"
if category.name == "毕设选题"
"/courses/#{course.id}/graduation_topics/#{category.course_module_id}"
else
"/courses/#{course.id}/graduation_tasks/#{category.course_module_id}"
end
when "attachment"
"/courses/#{course.id}/file/#{category.id}"
when "video"
"/courses/#{course.id}/course_video/#{category.id}"
end
end
@ -113,6 +115,8 @@ module CoursesHelper
end
when "attachment"
get_attachment_count(course, category.id)
when "video"
get_video_count(course, category.id)
end
end
@ -237,6 +241,11 @@ module CoursesHelper
category_id.to_i == 0 ? course.attachments.size : course.attachments.where(course_second_category_id: category_id).size
end
# 获取课堂的视频数
def get_video_count(course, category_id)
category_id.to_i == 0 ? course.course_videos.size : course.course_videos.where(course_second_category_id: category_id).size
end
# 获取课堂的作业数
def get_homework_commons_count(course, type, category_id)
category_id == 0 ? HomeworkCommon.where(course_id: course.id, homework_type: type).size :

@ -69,12 +69,13 @@ class Challenge < ApplicationRecord
end
# 开启挑战
def open_game user_id, shixun
game = self.games.where(user_id: user_id).first
if game.present?
shixun.task_pass || game.status != 3 ? "/tasks/#{game.identifier}" : ""
def open_game shixun
# 这里的identifier,status是关联了games取了games的identifier,status
identifier = self.identifier
if identifier.present?
shixun.task_pass || self.status != 3 ? "/tasks/#{identifier}" : ""
else
"/api/shixuns/#{shixun.identifier}/shixun_exec"
self.position == 1 ? "/api/shixuns/#{shixun.identifier}/shixun_exec" : ""
end
end
@ -92,16 +93,16 @@ class Challenge < ApplicationRecord
# end
## 用户关卡状态 0: 不能开启实训; 1:直接开启; 2表示已完成
def user_tpi_status user_id
def user_tpi_status shixun
# todo: 以前没加索引导致相同关卡,同一用户有多个games
# 允许跳关则直接开启
game = games.where(user_id: user_id).take
if game.blank?
position == 1 ? 1 : 0
identifier = self.identifier
if identifier.blank?
self.position == 1 ? 1 : 0
else
if game.status == 3
if status == 3
shixun.task_pass ? 1 : 0
elsif game.status == 2
elsif status == 2
2
else
1

@ -1,7 +1,7 @@
class CourseAttendance < ApplicationRecord
# status: 0: 未开启1已开启2已截止
# mode: 0 两种签到1 二维码签到2 数字签到
enum mode: { ALL: 0, QRCODE: 1, NUMBER: 2 }
# mode: 0 两种签到1 二维码签到2 数字签到3 快捷签到
enum mode: { ALL: 0, QRCODE: 1, NUMBER: 2, QUICK: 3 }
belongs_to :course
belongs_to :user

@ -1,6 +1,6 @@
class CourseMemberAttendance < ApplicationRecord
# attendance_mode 0 初始数据1 二维码签到2 数字签到3 老师签到
enum attendance_mode: { DEFAULT: 0, QRCODE: 1, NUMBER: 2, TEACHER: 3}
enum attendance_mode: { DEFAULT: 0, QRCODE: 1, NUMBER: 2, QUICK: 3, TEACHER: 4}
# attendance_status 1 正常签到2 请假0 旷课
enum attendance_status: { NORMAL: 1, LEAVE: 2, ABSENCE: 0 }
belongs_to :course_member

@ -1,4 +1,7 @@
class CourseVideo < ApplicationRecord
belongs_to :course
belongs_to :video
belongs_to :video, optional: true
belongs_to :user, optional: true
validates :title, length: { maximum: 60, too_long: "不能超过60个字符" }
end

@ -41,7 +41,8 @@ class Videos::BatchPublishService < ApplicationService
# 如果是课堂上传则创建课堂记录
Rails.logger.info("#####param: #{ param[:course_id]}")
if param[:course_id].present?
video.course_videos.create!(course_id: param[:course_id])
course_second_category_id = params[:category_id] || 0
video.course_videos.create!(course_id: param[:course_id], course_second_category_id: course_second_category_id)
end
end
end

@ -1,5 +1,6 @@
json.id attachment.id
json.title attachment.title
json.link attachment.link
json.is_public attachment.publiced?
# json.is_lock attachment.locked?(@is_member)
json.is_lock !attachment.publiced?
@ -15,4 +16,4 @@ json.created_on attachment.created_on
json.content_type attachment.content_type
json.is_pdf attachment.is_pdf?
json.url attachment.is_pdf? ? download_url(attachment,disposition:"inline") : download_url(attachment)
json.play_url attachment_show_users_path(file_name: local_path(attachment))
json.play_url attachment.link.present? ? nil : attachment_show_users_path(file_name: local_path(attachment))

@ -16,10 +16,12 @@ if @challenges.present?
json.st challenge.st
json.name challenge.subject
json.score challenge.score
json.passed_count challenge.user_passed_count
json.playing_count challenge.playing_count
json.passed_count @pass_games_map.fetch(challenge.id, 0)
#json.passed_count challenge.user_passed_count
json.playing_count @play_games_map.fetch(challenge.id, 0)
#json.playing_count challenge.playing_count
json.name_url shixun_challenge_path(challenge, shixun_identifier: @shixun.identifier)
#json.open_game challenge.open_game(@user.id, @shixun)
json.open_game challenge.open_game(@shixun)
if @editable
json.edit_url edit_shixun_challenge_path(challenge, shixun_identifier: @shixun.identifier)
json.delete_url shixun_challenge_path(challenge, shixun_identifier: @shixun.identifier)
@ -27,6 +29,6 @@ if @challenges.present?
json.down_url index_down_shixun_challenge_path(challenge, :shixun_identifier => @shixun.identifier) if @shixun.challenges_count != challenge.position
end
#json.passed challenge.has_passed?(@user.id)
json.status challenge.user_tpi_status @user.id
json.status challenge.user_tpi_status(@shixun)
end
end

@ -0,0 +1,8 @@
json.course_module do
json.id @course_module.id
json.module_name @course_module.module_name
json.module_type @course_module.module_type
json.course_second_categories do
json.array! @course_module.course_second_categories, :id, :name
end
end

@ -1,3 +1,22 @@
json.count @count
json.videos @videos, partial: 'users/videos/video', as: :video
json.course_id @course.id
json.videos @videos do |video|
if video.is_link
json.(video, :id, :title, :link, :user_id)
user = video.user
json.user_name user&.real_name
json.user_img url_to_avatar(user)
json.user_login user&.login
else
json.partial! 'users/videos/video', locals: { video: video.video }
end
end
json.course_id @course.id
if @category.present?
json.category_id @category.id
json.category_name @category.name
end
json.course_module_id @video_module&.id
json.has_category @video_module.course_second_categories.size > 0

@ -1,4 +1,4 @@
json.(attendance, :name, :mode)
json.(attendance, :id, :name, :mode)
json.attendance_date attendance.attendance_date.strftime("%Y/%m/%d")
json.start_time attendance.start_time.strftime("%H:%M")
json.end_time attendance.end_time.strftime("%H:%M")

@ -1,15 +1,17 @@
json.current_attendance @current_attendance do |attendance|
json.(attendance, :id, :normal_count, :all_count)
json.attendance_date attendance.attendance_date.strftime("%Y/%m/%d")
json.(attendance, :id, :name, :normal_count, :all_count)
json.attendance_date attendance.attendance_date.strftime("%Y-%m-%d")
json.start_time attendance.start_time.strftime("%H:%M")
json.end_time attendance.end_time.strftime("%H:%M")
end
all_normal_rate = []
all_absence_rate = []
all_leave_rate = []
json.history_attendances @history_attendances.each_with_index.to_a do |attendance, index|
normal_count = history_member_count(@all_member_attendances, "NORMAL", attendance.id)
absence_count = history_member_count(@all_member_attendances, "ABSENCE", attendance.id)
leave_count = history_member_count(@all_member_attendances, "LEAVE", attendance.id)
all_count = @all_member_attendances.select{|member_attendance| member_attendance.course_attendance_id == attendance.id}.size
json.index index + 1
@ -17,8 +19,11 @@ json.history_attendances @history_attendances.each_with_index.to_a do |attendanc
all_normal_rate << cal_rate(normal_count, all_count)
json.absence_rate cal_rate(absence_count, all_count)
all_absence_rate << cal_rate(absence_count, all_count)
json.leave_rate cal_rate(leave_count, all_count)
all_leave_rate << cal_rate(leave_count, all_count)
end
json.all_history_count @all_history_count
json.avg_normal_rate @all_history_count == 0 ? 0 : all_normal_rate.sum / @all_history_count
json.avg_absence_rate @all_history_count == 0 ? 0 : all_absence_rate.sum / @all_history_count
json.avg_leave_rate @all_history_count == 0 ? 0 : all_leave_rate.sum / @all_history_count

@ -5,7 +5,8 @@ json.all_count @all_count
json.code @attendance.attendance_code
json.mode @attendance.mode
json.edit_auth @user_course_identity < Course::PROFESSOR || @attendance.user_id == User.current.id
json.attendance_date @attendance.attendance_date.strftime("%Y/%m/%d")
json.name @attendance.name
json.attendance_date @attendance.attendance_date.strftime("%Y-%m-%d")
json.start_time @attendance.start_time.strftime("%H:%M")
json.end_time @attendance.end_time.strftime("%H:%M")

@ -1,4 +1,4 @@
json.current_attendance @current_attendance do |attendance|
json.current_attendance @current_attendances do |attendance|
json.partial! 'student_attendance', locals: {attendance: attendance}
end

@ -183,6 +183,9 @@ zh-CN:
attendance_date: '签到日期'
start_time: '开始时间'
end_time: '结束时间'
course_video:
title: '视频名称'
link: '链接地址'

@ -195,7 +195,7 @@ Rails.application.routes.draw do
resource :unread_message_info, only: [:show]
# 视频
resources :videos, only: [:index, :update] do
resources :videos, only: [:index, :update, :destroy] do
collection do
get :review
post :batch_publish
@ -464,6 +464,7 @@ Rails.application.routes.draw do
end
member do
get :histories
post :update_visits
end
end
@ -524,6 +525,7 @@ Rails.application.routes.draw do
get 'statistics'
get 'course_videos'
delete 'delete_course_video'
post :move_to_category
post :inform_up
post :inform_down
get :calculate_all_shixun_scores
@ -537,6 +539,8 @@ Rails.application.routes.draw do
get 'search_slim'
end
resources :course_videos, only:[:create, :update], shallow: true
resources :course_stages, shallow: true do
member do
post :up_position

@ -0,0 +1,5 @@
class AddCourseSecondCategoryIdToCourseVideos < ActiveRecord::Migration[5.2]
def change
add_column :course_videos, :course_second_category_id, :integer, index: true, default: 0
end
end

@ -0,0 +1,5 @@
class MigrateMemberAttendanceMode < ActiveRecord::Migration[5.2]
def change
CourseMemberAttendance.where(attendance_mode: 3).update_all(attendance_mode: 4)
end
end

@ -0,0 +1,8 @@
class AddLinkToCourseVideos < ActiveRecord::Migration[5.2]
def change
add_column :course_videos, :is_link, :boolean, default: 0
add_column :course_videos, :title, :string
add_column :course_videos, :link, :string
add_column :course_videos, :user_id, :integer, index: true
end
end

@ -0,0 +1,5 @@
class AddLinkToAttachments < ActiveRecord::Migration[5.2]
def change
add_column :attachments, :link, :string
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

@ -30,6 +30,54 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe734;</span>
<div class="name">移动</div>
<div class="code-name">&amp;#xe734;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe732;</span>
<div class="name">下移2</div>
<div class="code-name">&amp;#xe732;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe731;</span>
<div class="name">上移2</div>
<div class="code-name">&amp;#xe731;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe730;</span>
<div class="name">下移</div>
<div class="code-name">&amp;#xe730;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe72f;</span>
<div class="name">上移</div>
<div class="code-name">&amp;#xe72f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe72e;</span>
<div class="name">编辑</div>
<div class="code-name">&amp;#xe72e;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe72d;</span>
<div class="name">删除</div>
<div class="code-name">&amp;#xe72d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe72c;</span>
<div class="name">选择</div>
<div class="code-name">&amp;#xe72c;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe72a;</span>
<div class="name">编辑</div>
@ -2012,6 +2060,78 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-yidong"></span>
<div class="name">
移动
</div>
<div class="code-name">.icon-yidong
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xiayi1"></span>
<div class="name">
下移2
</div>
<div class="code-name">.icon-xiayi1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shangyi1"></span>
<div class="name">
上移2
</div>
<div class="code-name">.icon-shangyi1
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xiayi"></span>
<div class="name">
下移
</div>
<div class="code-name">.icon-xiayi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shangyi"></span>
<div class="name">
上移
</div>
<div class="code-name">.icon-shangyi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-bianji5"></span>
<div class="name">
编辑
</div>
<div class="code-name">.icon-bianji5
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-shanchu3"></span>
<div class="name">
删除
</div>
<div class="code-name">.icon-shanchu3
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xuanze"></span>
<div class="name">
选择
</div>
<div class="code-name">.icon-xuanze
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-bianji4"></span>
<div class="name">
@ -4939,6 +5059,70 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-yidong"></use>
</svg>
<div class="name">移动</div>
<div class="code-name">#icon-yidong</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xiayi1"></use>
</svg>
<div class="name">下移2</div>
<div class="code-name">#icon-xiayi1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shangyi1"></use>
</svg>
<div class="name">上移2</div>
<div class="code-name">#icon-shangyi1</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xiayi"></use>
</svg>
<div class="name">下移</div>
<div class="code-name">#icon-xiayi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shangyi"></use>
</svg>
<div class="name">上移</div>
<div class="code-name">#icon-shangyi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-bianji5"></use>
</svg>
<div class="name">编辑</div>
<div class="code-name">#icon-bianji5</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-shanchu3"></use>
</svg>
<div class="name">删除</div>
<div class="code-name">#icon-shanchu3</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xuanze"></use>
</svg>
<div class="name">选择</div>
<div class="code-name">#icon-xuanze</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-bianji4"></use>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -5,6 +5,62 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "13353315",
"name": "移动",
"font_class": "yidong",
"unicode": "e734",
"unicode_decimal": 59188
},
{
"icon_id": "13247262",
"name": "下移2",
"font_class": "xiayi1",
"unicode": "e732",
"unicode_decimal": 59186
},
{
"icon_id": "13247261",
"name": "上移2",
"font_class": "shangyi1",
"unicode": "e731",
"unicode_decimal": 59185
},
{
"icon_id": "13247178",
"name": "下移",
"font_class": "xiayi",
"unicode": "e730",
"unicode_decimal": 59184
},
{
"icon_id": "13247175",
"name": "上移",
"font_class": "shangyi",
"unicode": "e72f",
"unicode_decimal": 59183
},
{
"icon_id": "13247173",
"name": "编辑",
"font_class": "bianji5",
"unicode": "e72e",
"unicode_decimal": 59182
},
{
"icon_id": "13247168",
"name": "删除",
"font_class": "shanchu3",
"unicode": "e72d",
"unicode_decimal": 59181
},
{
"icon_id": "13183780",
"name": "选择",
"font_class": "xuanze",
"unicode": "e72c",
"unicode_decimal": 59180
},
{
"icon_id": "2077714",
"name": "编辑",

@ -20,6 +20,30 @@ Created by iconfont
/>
<missing-glyph />
<glyph glyph-name="yidong" unicode="&#59188;" d="M855.341176-121.976471H174.682353c-90.352941 0-156.611765 72.282353-156.611765 156.611765V721.317647C18.070588 811.670588 90.352941 877.929412 174.682353 877.929412h680.658823c90.352941 0 156.611765-72.282353 156.611765-156.611765v-78.305882h-90.352941V721.317647c0 36.141176-30.117647 66.258824-66.258824 66.258824H174.682353c-36.141176 0-66.258824-30.117647-66.258824-66.258824v-680.658823c0-36.141176 30.117647-66.258824 66.258824-66.258824h680.658823c36.141176 0 66.258824 30.117647 66.258824 66.258824v78.305882h90.352941v-78.305882c6.023529-90.352941-66.258824-162.635294-156.611765-162.635295zM951.717647 299.670588H271.058824c-24.094118 0-48.188235 18.070588-48.188236 48.188236s18.070588 48.188235 48.188236 48.188235h680.658823c24.094118 0 48.188235-18.070588 48.188235-48.188235s-24.094118-48.188235-48.188235-48.188236zM1084.235294 347.858824L921.6 173.176471V534.588235L1084.235294 359.905882v-12.047058z" horiz-adv-x="1084" />
<glyph glyph-name="xiayi1" unicode="&#59186;" d="M512 384m-481.882353 0a481.882353 481.882353 0 1 1 963.764706 0 481.882353 481.882353 0 1 1-963.764706 0ZM512-128C228.894118-128 0 100.894118 0 384S228.894118 896 512 896 1024 667.105882 1024 384 795.105882-128 512-128z m0 963.764706C265.035294 835.764706 60.235294 630.964706 60.235294 384S265.035294-67.764706 512-67.764706 963.764706 137.035294 963.764706 384 758.964706 835.764706 512 835.764706zM502.964706 143.058824c-18.070588 0-30.117647 12.047059-30.117647 30.117647V588.8c0 18.070588 12.047059 30.117647 30.117647 30.117647s30.117647-12.047059 30.117647-30.117647V173.176471c0-18.070588-12.047059-30.117647-30.117647-30.117647zM502.964706 88.847059c-6.023529 0-12.047059 0-18.070588 6.023529l-144.564706 132.517647c-12.047059 12.047059-12.047059 30.117647 0 42.164706 12.047059 12.047059 30.117647 12.047059 42.164706 0l126.494117-108.423529 132.517647 108.423529c12.047059 12.047059 30.117647 12.047059 42.164706-6.023529 6.023529-6.023529 6.023529-30.117647-12.047059-36.141177l-150.588235-132.517647c-6.023529 0-12.047059-6.023529-18.070588-6.023529z" horiz-adv-x="1024" />
<glyph glyph-name="shangyi1" unicode="&#59185;" d="M512 384m-481.882353 0a481.882353 481.882353 0 1 1 963.764706 0 481.882353 481.882353 0 1 1-963.764706 0ZM512-128C228.894118-128 0 100.894118 0 384S228.894118 896 512 896 1024 667.105882 1024 384 795.105882-128 512-128z m0 963.764706C265.035294 835.764706 60.235294 630.964706 60.235294 384S265.035294-67.764706 512-67.764706 963.764706 137.035294 963.764706 384 758.964706 835.764706 512 835.764706zM502.964706 106.917647c-18.070588 0-30.117647 12.047059-30.117647 30.117647V552.658824c0 18.070588 12.047059 30.117647 30.117647 30.117647s30.117647-12.047059 30.117647-30.117647v-415.62353c0-18.070588-12.047059-30.117647-30.117647-30.117647zM653.552941 444.235294c-6.023529 0-12.047059 0-18.070588 6.02353l-132.517647 114.447058-126.494118-108.423529c-12.047059-12.047059-30.117647-12.047059-42.164706 0-12.047059 12.047059-12.047059 30.117647 0 42.164706l144.564706 126.494117c12.047059 12.047059 30.117647 12.047059 42.164706 0l150.588235-126.494117c12.047059-12.047059 12.047059-30.117647 6.02353-42.164706-6.023529-6.023529-12.047059-12.047059-24.094118-12.047059z" horiz-adv-x="1024" />
<glyph glyph-name="xiayi" unicode="&#59184;" d="M512 384m-481.882353 0a481.882353 481.882353 0 1 1 963.764706 0 481.882353 481.882353 0 1 1-963.764706 0ZM512-128C228.894118-128 0 100.894118 0 384S228.894118 896 512 896 1024 667.105882 1024 384 795.105882-128 512-128z m0 963.764706C265.035294 835.764706 60.235294 630.964706 60.235294 384S265.035294-67.764706 512-67.764706 963.764706 137.035294 963.764706 384 758.964706 835.764706 512 835.764706zM502.964706 143.058824c-18.070588 0-30.117647 12.047059-30.117647 30.117647V588.8c0 18.070588 12.047059 30.117647 30.117647 30.117647s30.117647-12.047059 30.117647-30.117647V173.176471c0-18.070588-12.047059-30.117647-30.117647-30.117647zM502.964706 88.847059c-6.023529 0-12.047059 0-18.070588 6.023529l-144.564706 132.517647c-12.047059 12.047059-12.047059 30.117647 0 42.164706 12.047059 12.047059 30.117647 12.047059 42.164706 0l126.494117-108.423529 132.517647 108.423529c12.047059 12.047059 30.117647 12.047059 42.164706-6.023529 6.023529-6.023529 6.023529-30.117647-12.047059-36.141177l-150.588235-132.517647c-6.023529 0-12.047059-6.023529-18.070588-6.023529z" horiz-adv-x="1024" />
<glyph glyph-name="shangyi" unicode="&#59183;" d="M512 384m-481.882353 0a481.882353 481.882353 0 1 1 963.764706 0 481.882353 481.882353 0 1 1-963.764706 0ZM512-128C228.894118-128 0 100.894118 0 384S228.894118 896 512 896 1024 667.105882 1024 384 795.105882-128 512-128z m0 963.764706C265.035294 835.764706 60.235294 630.964706 60.235294 384S265.035294-67.764706 512-67.764706 963.764706 137.035294 963.764706 384 758.964706 835.764706 512 835.764706zM502.964706 106.917647c-18.070588 0-30.117647 12.047059-30.117647 30.117647V552.658824c0 18.070588 12.047059 30.117647 30.117647 30.117647s30.117647-12.047059 30.117647-30.117647v-415.62353c0-18.070588-12.047059-30.117647-30.117647-30.117647zM653.552941 444.235294c-6.023529 0-12.047059 0-18.070588 6.02353l-132.517647 114.447058-126.494118-108.423529c-12.047059-12.047059-30.117647-12.047059-42.164706 0-12.047059 12.047059-12.047059 30.117647 0 42.164706l144.564706 126.494117c12.047059 12.047059 30.117647 12.047059 42.164706 0l150.588235-126.494117c12.047059-12.047059 12.047059-30.117647 6.02353-42.164706-6.023529-6.023529-12.047059-12.047059-24.094118-12.047059z" horiz-adv-x="1024" />
<glyph glyph-name="bianji5" unicode="&#59182;" d="M704.752941-97.882353H198.776471c-78.305882 0-138.541176 60.235294-138.541177 132.517647V667.105882c0 72.282353 66.258824 132.517647 138.541177 132.517647h319.247058c18.070588 0 30.117647-12.047059 30.117647-30.117647s-12.047059-24.094118-30.117647-24.094117H198.776471c-42.164706 0-78.305882-36.141176-78.305883-72.282353v-632.470588c0-42.164706 36.141176-72.282353 78.305883-72.282353h499.952941c42.164706 0 78.305882 36.141176 78.305882 72.282353V293.647059c0 18.070588 12.047059 30.117647 30.117647 30.117647s30.117647-12.047059 30.117647-30.117647v-259.011765c6.023529-72.282353-60.235294-132.517647-132.517647-132.517647zM313.223529 161.129412c-24.094118 0-54.211765 18.070588-60.235294 48.188235v30.117647l36.141177 126.494118 445.741176 445.741176c48.188235 48.188235 120.470588 48.188235 168.658824 0 48.188235-48.188235 48.188235-120.470588 0-168.658823l-445.741177-445.741177-126.494117-30.117647c-6.023529-6.023529-12.047059-6.023529-18.070589-6.023529z m36.141177 174.682353l-36.141177-114.447059 120.470589 30.117647 433.694117 433.694118c12.047059 12.047059 18.070588 24.094118 18.070589 42.164705 0 18.070588-6.023529 30.117647-18.070589 42.164706-24.094118 24.094118-60.235294 24.094118-84.329411 0L349.364706 335.811765z" horiz-adv-x="1024" />
<glyph glyph-name="shanchu3" unicode="&#59181;" d="M654.222222-99.555556H375.466667c-68.266667 0-125.155556 56.888889-136.533334 142.222223L142.222222 571.733333c0 17.066667 5.688889 28.444444 22.755556 34.133334 17.066667 0 28.444444-5.688889 34.133333-22.755556l91.022222-534.755555c11.377778-51.2 45.511111-91.022222 85.333334-91.022223h278.755555c39.822222 0 73.955556 39.822222 85.333334 91.022223l91.022222 534.755555c0 17.066667 17.066667 28.444444 34.133333 22.755556 17.066667 0 28.444444-17.066667 22.755556-34.133334L796.444444 42.666667c-17.066667-85.333333-73.955556-142.222222-142.222222-142.222223zM597.333333 708.266667v5.688889c0 51.2-39.822222 96.711111-85.333333 96.711111s-85.333333-45.511111-85.333333-102.4h-56.888889C369.777778 793.6 432.355556 867.555556 512 867.555556c73.955556 0 136.533333-68.266667 142.222222-147.911112v-5.688888l-56.888889-5.688889zM398.222222 36.977778c-11.377778 0-28.444444 11.377778-28.444444 22.755555L284.444444 577.422222c0 17.066667 11.377778 34.133333 22.755556 34.133334 17.066667 5.688889 34.133333-5.688889 34.133333-22.755556l85.333334-517.688889c0-17.066667-5.688889-34.133333-28.444445-34.133333 5.688889 0 0 0 0 0zM625.777778 36.977778s-5.688889 0 0 0c-22.755556 5.688889-28.444444 17.066667-28.444445 34.133333L682.666667 588.8c0 17.066667 17.066667 28.444444 34.133333 22.755556 11.377778 0 22.755556-17.066667 22.755556-34.133334l-85.333334-517.688889c0-11.377778-17.066667-22.755556-28.444444-22.755555zM512 36.977778c-17.066667 0-28.444444 11.377778-28.444444 28.444444V583.111111c0 17.066667 11.377778 28.444444 28.444444 28.444445s28.444444-11.377778 28.444444-28.444445v-517.688889c0-17.066667-11.377778-28.444444-28.444444-28.444444zM938.666667 645.688889H85.333333c-17.066667 0-28.444444 17.066667-28.444444 28.444444s11.377778 28.444444 28.444444 28.444445h853.333334c17.066667 0 28.444444-11.377778 28.444444-28.444445s-11.377778-28.444444-28.444444-28.444444z" horiz-adv-x="1024" />
<glyph glyph-name="xuanze" unicode="&#59180;" d="M870.4 896H153.6C66.56 896 0 829.44 0 742.4v-716.8c0-87.04 66.56-153.6 153.6-153.6h716.8c87.04 0 153.6 66.56 153.6 153.6V742.4c0 87.04-66.56 153.6-153.6 153.6z m-40.96-394.24l-337.92-358.4c-10.24-10.24-25.6-15.36-40.96-15.36-10.24 0-20.48 5.12-30.72 10.24L174.08 332.8c-20.48 15.36-25.6 51.2-10.24 71.68 15.36 20.48 51.2 25.6 71.68 10.24l209.92-163.84 307.2 327.68c20.48 20.48 51.2 20.48 71.68 0 20.48-20.48 25.6-56.32 5.12-76.8z" horiz-adv-x="1024" />
<glyph glyph-name="bianji4" unicode="&#59178;" d="M934.724189 617.173271L834.414864 516.863946 653.380212 697.898598l100.394659 100.351991A42.257063 42.257063 0 0 0 783.769535 810.666588c6.527999 0 19.157332-1.621333 29.951997-12.415999l121.002657-121.002656a42.538663 42.538663 0 0 0 0-60.074662zM299.374908-18.176009l-200.661316-19.626665 19.669331 200.661316L593.049551 637.52527 774.084202 456.533285l-474.709294-474.709294z m695.679942 755.79727L874.09486 858.581251A127.317323 127.317323 0 0 1 783.769535 895.999915c-32.725331 0-65.407995-12.458666-90.367993-37.418664l-646.229279-646.229279c-7.082666-7.082666-11.434666-16.469332-12.287999-26.495998l-26.197331-267.818645c-2.133333-25.002665 17.706665-46.037329 42.239996-46.037329 1.194667 0 2.432 0.042667 3.626666 0.128l267.818645 26.239998a42.12053 42.12053 0 0 1 26.495998 12.287999l646.186612 646.186613c49.919996 49.919996 49.919996 130.858656 0 180.778651z" horiz-adv-x="1032" />

Before

Width:  |  Height:  |  Size: 394 KiB

After

Width:  |  Height:  |  Size: 404 KiB

@ -10,11 +10,9 @@ 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 {formatDelta} from './util';
const FormItem = Form.Item;
function CommentForm (props) {
function CommentForm(props) {
const {
onCancel,
@ -28,9 +26,6 @@ function CommentForm (props) {
const [focus, setFocus] = useState(false);
const options = [
// ['bold', 'italic', 'underline'],
// [{header: [1,2,3,false]}],
'code-block',
'link',
'image',
'formula'
@ -52,12 +47,10 @@ function CommentForm (props) {
// 编辑器内容变化时
const handleContentChange = (content) => {
console.log('编辑器内容', content);
setCtx(content);
try {
// const _html = new QuillDeltaToHtmlConverter(content.ops, {}).convert();
// props.form.setFieldsValue({'comment': _html.replace(/<\/?[^>]*>/g, '')});
props.form.setFieldsValue({'comment': content});
props.form.setFieldsValue({ 'comment': content });
} catch (error) {
console.log(error);
}
@ -69,7 +62,7 @@ function CommentForm (props) {
if (!err) {
setShowQuill(false);
const content = ctx;
props.form.setFieldsValue({'comment': ''});
props.form.setFieldsValue({ 'comment': '' });
setCtx('');
// const _html = formatDelta(content.ops);
// console.log('保存的内容=====》》》》', content);
@ -95,7 +88,7 @@ function CommentForm (props) {
{
getFieldDecorator('comment', {
rules: [
{ required: true, message: '评论内容不能为空'}
{ required: true, message: '评论内容不能为空' }
],
})(
<Input
@ -112,7 +105,7 @@ function CommentForm (props) {
}
<QuillForEditor
imgAttrs={{width: '60px', height: '30px'}}
imgAttrs={{ width: '60px', height: '30px' }}
wrapStyle={{
height: showQuill ? 'auto' : '0px',
opacity: showQuill ? 1 : 0,
@ -130,7 +123,7 @@ function CommentForm (props) {
</FormItem>
<FormItem style={{ textAlign: 'right', display: showQuill ? 'block' : 'none' }}>
<Button onClick={handleCancle}>取消</Button>
<Button onClick={handleSubmit} type="primary" style={{ marginLeft: '10px'}}>发送</Button>
<Button onClick={handleSubmit} type="primary" style={{ marginLeft: '10px' }}>发送</Button>
</FormItem>
</Form>
);

@ -7,10 +7,6 @@
* @LastEditTime : 2019-12-27 11:05:17
*/
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'
@ -18,7 +14,7 @@ import { Icon } from 'antd';
import CommentForm from './CommentForm';
import QuillForEditor from '../../quillForEditor';
function CommentItem ({
function CommentItem({
isAdmin,
options,
confirm,
@ -39,7 +35,7 @@ function CommentItem ({
// 上传图片的ulr
const [url, setUrl] = useState('');
const {
const {
author = {}, // 作者
id, // 评论id
content, // 回复内容
@ -57,22 +53,21 @@ function CommentItem ({
confirm({
title: '提示',
content: ('确定要删除该条回复吗?'),
onOk () {
console.log('点击了删除', id);
onOk() {
submitDeleteComment && submitDeleteComment(id);
}
});
}
// 评论头像
const commentAvatar = (author) => (
<img
className="item-flex flex-image"
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 = (id, author, time, can_delete) => {
const _classNames = can_delete ? 'item-close' : 'item-close hide';
@ -84,11 +79,10 @@ function CommentItem ({
<span className="iconfont icon-shanchu icon_font_size_14" onClick={() => deleteComment(id)}></span>
</span>
</div>
);
);
};
const handleShowUploadImage = (url) => {
// console.log('==============>>>>>>>>>>>>',url);
setUrl(url);
}
// 评论内容
@ -105,7 +99,8 @@ function CommentItem ({
value={_ctx}
showUploadImage={handleShowUploadImage}
/>
)};
)
};
// 加载更多
const handleOnLoadMore = (len) => {
@ -154,7 +149,7 @@ function CommentItem ({
<li className={_moreClass} onClick={() => handleOnLoadMore(len)}>
<p className="loadmore-txt">展开其余{lastTxt}条评论</p>
<p className="loadmore-icon">
<Icon type={!arrow ? 'down' : 'up'}/>
<Icon type={!arrow ? 'down' : 'up'} />
</p>
</li>
</ul>
@ -169,7 +164,7 @@ function CommentItem ({
const handleClickLick = (id) => {
likeComment && likeComment(id);
}
// 点击评论icon
const handleClickMessage = () => {
setShowQuill(true);
@ -198,33 +193,33 @@ function CommentItem ({
<div className="item-flex item-desc">
{commentInfo(id, author, time, can_delete)}
{commentCtx(content)}
{commentAppend(children)}
<div className="comment_icon_area">
<CommentIcon
style={{ display: isAdmin ? 'inline-block' : 'none'}}
className='comment-icon-margin'
type={!hidden ? "xianshi" : 'yincang1'}
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'}
style={{ display: can_delete ? 'inline-block' : 'none' }}
className='comment-icon-margin'
type={'shanchu'}
iconClick={() => deleteComment(id)}
/>
{/* 回复 */}
<CommentIcon
className='comment-icon-margin'
<CommentIcon
className='comment-icon-margin'
type="huifu1"
count={children.length}
count={children.length}
iconClick={handleClickMessage}
/>
{/* 点赞 */}
<CommentIcon
iconColor={ user_praise ? '#5091FF' : '' }
<CommentIcon
iconColor={user_praise ? '#5091FF' : ''}
className='comment-icon-margin'
theme={user_praise ? 'filled' : ''}
type="dianzan"
@ -234,7 +229,7 @@ function CommentItem ({
</div>
<div
style={{ display: showQuill ? 'block' : 'none'}}
style={{ display: showQuill ? 'block' : 'none' }}
className="comment_item_quill">
<CommentForm
onCancel={handleClickCancel}
@ -242,11 +237,10 @@ function CommentItem ({
/>
</div>
{/* 显示上传的图片信息 */}
<div className="show_upload_image" style={{ display: url ? 'block' : 'none'}}>
<Icon type="close" className="image_close" onClick={handleClose}/>
<div className="show_upload_image" style={{ display: url ? 'block' : 'none' }}>
<Icon type="close" className="image_close" onClick={handleClose} />
<div className="image_info">
<img className="image" src={url} alt=""/>
<img className="image" src={url} alt="" />
</div>
</div>
</div>
@ -254,4 +248,4 @@ function CommentItem ({
);
}
export default CNotificationHOC() (CommentItem);
export default CNotificationHOC()(CommentItem);

@ -7,39 +7,28 @@
* @LastEditTime : 2019-12-24 18:03:21
*/
import React from 'react';
// import CommentForm from './CommentForm';
import CommentList from './CommentList';
function Comment (props) {
function Comment(props) {
const {
const {
commentLists,
// addComment,
// cancelComment,
isAdmin,
addChildComment,
likeComment,
showOrHideComment,
submitDeleteComment
} = props;
// const handleCancelComment = () => {
// cancelComment && cancelComment();
// };
return (
<React.Fragment>
{/* <CommentForm
onCancel={handleCancelComment}
onSubmit={addComment}
/> */}
<CommentList
isAdmin={isAdmin}
likeComment={likeComment}
showOrHideComment={showOrHideComment}
commentLists={commentLists}
submitChildComment={addChildComment}
submitDeleteComment={submitDeleteComment}
/>
</React.Fragment>
<CommentList
isAdmin={isAdmin}
likeComment={likeComment}
showOrHideComment={showOrHideComment}
commentLists={commentLists}
submitChildComment={addChildComment}
submitDeleteComment={submitDeleteComment}
/>
);
}

@ -8,19 +8,15 @@
*/
import Quill from 'quill';
let Inline = Quill.import('blots/inline');
// const BlockEmbed = Quill.import('blots/embed');
class FillBlot extends Inline {
static create (value) {
static create(value) {
const node = super.cerate(value);
// node.classList.add('icon icon-bianji2');
// node.setAttribute('data-fill', 'fill');
console.log('编辑器值===》》》》》', value);
node.setAttribute('data_index', value.data_index);
node.nodeValue = value.text;
node.nodeValue = value.text;
return node;
}
static value (node) {
static value(node) {
return {
// dataSet: node.getAttribute('data-fill'),
data_index: node.getAttribute('data_index')

@ -17,7 +17,6 @@ export default class ImageBlot extends BlockEmbed {
const node = super.create();
node.setAttribute('alt', value.alt);
node.setAttribute('src', value.url);
// console.log('~~~~~~~~~~~', node, value);
node.addEventListener('click', function () {
value.onclick(value.url);
}, false);
@ -33,25 +32,14 @@ export default class ImageBlot extends BlockEmbed {
}
// 宽度和高度都不存在时,
if (!value.width && !value.height) {
// node.setAttribute('display', 'block');
node.setAttribute('width', '100%');
}
// node.setAttribute('style', { cursor: 'pointer' });
// if (node.onclick) {
// console.log('image 有图片点击事件======》》》》》》');
// // node.setAttribute('onclick', node.onCLick);
// }
// 给图片添加点击事件
// node.onclick = () => {
// value.onClick && value.onClick(value.url);
// }
return node;
}
// 获取节点值
static value (node) {
static value(node) {
return {
alt: node.getAttribute('alt'),
@ -61,7 +49,6 @@ export default class ImageBlot extends BlockEmbed {
height: node.height,
display: node.getAttribute('display'),
id: node.id,
// style: node.style
};
}
}

@ -20,16 +20,17 @@ import { fetchUploadImage } from '../../services/ojService.js';
import { getImageUrl } from 'educoder'
import ImageBlot from './ImageBlot';
import FillBlot from './FillBlot';
import LinkBlot from './link-blot'
var Size = Quill.import('attributors/style/size');
// const Color = Quill.import('attributes/style/color');
Size.whitelist = ['14px', '16px', '18px', '20px', false];
var fonts = ['Microsoft-YaHei','SimSun', 'SimHei','KaiTi','FangSong'];
var fonts = ['Microsoft-YaHei', 'SimSun', 'SimHei', 'KaiTi', 'FangSong'];
var Font = Quill.import('formats/font');
Font.whitelist = fonts; //将字体加入到白名单
window.Quill = Quill;
window.katex = katex;
Quill.register(ImageBlot);
Quill.register(Size);
Quill.register(LinkBlot);
Quill.register(Font, true);
// Quill.register({'modules/toolbar': Toolbar});
Quill.register({
@ -38,7 +39,7 @@ Quill.register({
// Quill.register(Color);
function QuillForEditor ({
function QuillForEditor({
placeholder,
readOnly,
autoFocus = false,
@ -51,17 +52,16 @@ function QuillForEditor ({
onContentChange,
addFill, // 点击填空成功的回调
deleteFill // 删除填空,返回删除的下标
// getQuillContent
}) {
// toolbar 默认值
const defaultConfig = [
'bold', 'italic', 'underline',
{size: ['14px', '16px', '18px', '20px']},
{align: []}, {list: 'ordered'}, {list: 'bullet'}, // 列表
{script: 'sub'}, {script: 'super'},
{ size: ['14px', '16px', '18px', '20px'] },
{ align: [] }, { list: 'ordered' }, { list: 'bullet' }, // 列表
{ script: 'sub' }, { script: 'super' },
{ 'color': [] }, { 'background': [] },
{ 'font': []},
{header: [1,2,3,4,5,false]},
{ 'font': [] },
{ header: [1, 2, 3, 4, 5, false] },
'blockquote', 'code-block',
'link', 'image', 'video',
'formula',
@ -77,7 +77,6 @@ function QuillForEditor ({
// 文本内容变化时
const handleOnChange = content => {
// getQuillContent && getQuillContent(quill);
onContentChange && onContentChange(content, quill);
};
@ -86,9 +85,7 @@ function QuillForEditor ({
const bindings = {
tab: {
key: 9,
handler: function () {
console.log('调用了tab=====>>>>');
}
handler: function () { }
},
backspace: {
key: 'Backspace',
@ -104,11 +101,10 @@ function QuillForEditor ({
* index: 删除元素的位置
* length: 删除元素的个数
*/
const {index, length} = range;
const { index, length } = range;
const _start = length === 0 ? index - 1 : index;
const _length = length || 1;
let delCtx = this.quill.getText(_start, _length); // 删除的元素
// aa
const reg = /▁/g;
const delArrs = delCtx.match(reg);
if (delArrs) {
@ -216,7 +212,7 @@ function QuillForEditor ({
const ops = value.ops || [];
ops.forEach((item, i) => {
if (item.insert['image']) {
item.insert['image'] = Object.assign({}, item.insert['image'], {style: { cursor: 'pointer' }, onclick: (url) => showUploadImage(url)});
item.insert['image'] = Object.assign({}, item.insert['image'], { style: { cursor: 'pointer' }, onclick: (url) => showUploadImage(url) });
}
});
}
@ -225,7 +221,7 @@ function QuillForEditor ({
if (!deepEqual(previous, current)) {
setSelection(quill.getSelection())
if (typeof value === 'string' && value) {
// debugger
// debugger
quill.clipboard.dangerouslyPasteHTML(value, 'api');
if (autoFocus) {
quill.focus();
@ -273,9 +269,9 @@ function QuillForEditor ({
// 返回结果
return (
<div className='quill_editor_for_react_area' style={wrapStyle}>
<div ref={editorRef} style={style}></div>
</div>
<div className='quill_editor_for_react_area' style={wrapStyle}>
<div ref={editorRef} style={style}></div>
</div>
);
}

@ -0,0 +1,21 @@
import Quill from "quill";
const Inline = Quill.import('blots/inline');
export default class LinkBlot extends Inline {
static create(value) {
let node = super.create()
let rs = value
if (rs.indexOf('http://') < 0) {
rs = 'http://' + rs
}
node.setAttribute('href', rs)
node.setAttribute('target', '_blank')
return node;
}
static formats(node) {
return node.getAttribute('href');
}
}
LinkBlot.blotName = 'link'
LinkBlot.tagName = 'a'

@ -1,54 +0,0 @@
/*
* @Description: 重写图片
* @Author: tangjiang
* @Github:
* @Date: 2019-12-16 15:50:45
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-17 16:44:48
*/
import Quill from "quill";
const BlockEmbed = Quill.import('blots/block/embed');
export default class ImageBlot extends BlockEmbed {
static create(value) {
const node = super.create();
node.setAttribute('alt', value.alt);
node.setAttribute('src', value.url);
if (value.width) {
node.setAttribute('width', value.width);
}
if (value.height) {
node.setAttribute('height', value.height);
}
// 宽度和高度都不存在时,
if (!value.width && !value.height) {
node.setAttribute('display', 'block');
node.setAttribute('width', '100%');
}
// 给图片添加点击事件
node.onclick = () => {
value.onClick && value.onClick(value.url);
}
return node;
}
static value (node) {
return {
alt: node.getAttribute('alt'),
url: node.getAttribute('src'),
onclick: node.onclick,
// width: node.width,
// height: node.height,
display: node.getAttribute('display')
};
}
}
ImageBlot.blotName = 'image';
ImageBlot.tagName = 'img';

@ -1,45 +0,0 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-09 09:09:42
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 08:46:20
*/
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, useReducer, useEffect } from 'react';
import useQuill from './useQuill';
function ReactQuill ({
disallowColors, // 不可见时颜色
placeholder, // 提示信息
uploadImage, // 图片上传
onChange, // 内容变化时
options, // 配置信息
value, // 显示的内容
style,
showUploadImage // 显示上传图片
}) {
const [element, setElement] = useState(); // quill 渲染节点
useQuill({
disallowColors,
placeholder,
uploadImage,
onChange,
options,
value,
showUploadImage,
element
});
return (
<div className='react_quill_area' ref={setElement} style={style}/>
);
}
export default ReactQuill;

@ -1,47 +0,0 @@
function deepEqual (prev, current) {
if (prev === current) { // 基本类型比较,值,类型都相同 或者同为 null or undefined
return true;
}
if ((!prev && current)
|| (prev && !current)
|| (!prev && !current)
) {
return false;
}
if (Array.isArray(prev)) {
if (!Array.isArray(current)) return false;
if (prev.length !== current.length) return false;
for (let i = 0; i < prev.length; i++) {
if (!deepEqual(current[i], prev[i])) {
return false;
}
}
return true;
}
if (typeof current === 'object') {
if (typeof prev !== 'object') return false;
const prevKeys = Object.keys(prev);
const curKeys = Object.keys(current);
if (prevKeys.length !== curKeys.length) return false;
prevKeys.sort();
curKeys.sort();
for (let i = 0; i < prevKeys.length; i++) {
if (prevKeys[i] !== curKeys[i]) return false;
const key = prevKeys[i];
if (!deepEqual(prev[key], current[key])) return false;
}
return true;
}
return false;
}
export default deepEqual;

@ -1,26 +0,0 @@
/*
* @Description: 将多维数组转变成一维数组
* @Author: tangjiang
* @Github:
* @Date: 2019-12-09 09:35:01
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-16 11:36:22
*/
function flatten (array) {
return flatten.rec(array, []);
}
flatten.rec = function flatten (array, result) {
for (let item of array) {
if (Array.isArray(item)) {
flatten(item, result);
} else {
result.push(item);
}
}
return result;
}
export default flatten;

@ -1,108 +0,0 @@
/*
* @Description: 入口文件
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 10:41:48
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-17 20:34:40
*/
import React, { useState, useCallback, useEffect } from 'react';
import ReactQuill from './lib';
function Wrapper (props) {
// 默认工具栏配置项
const toolbarConfig = [
['bold', 'italic', 'underline'],
[{align: []}, {list: 'ordered'}, {list: 'bullet'}], // 列表
[{script: 'sub'}, {script: 'super'}],
[{header: [1,2,3,4,5,false]}],
['blockquote', 'code-block'],
['link', 'image', 'video'],
['formula'],
['clean']
];
const [placeholder] = useState(props.placeholder || 'placeholder');
const [disableBold] = useState(false);
const [value, setValue] = useState(props.value || '');
const [toolbar, setToolbar] = useState(toolbarConfig);
const [theme, setTheme] = useState(props.theme || 'snow');
const [readOnly] = useState(props.readOnly || false);
const {
onContentChagne, // 当编辑器内容变化时调用该函数
showUploadImage, // 显示上传图片, 返回url主要用于点击图片放大
} = props;
// 配置信息
const options = {
modules: {
toolbar: toolbar,
clipboard: {
matchVisual: false
}
},
readOnly: readOnly,
theme: theme
}
// 配置信息
useEffect (() => {
if (props.options) {
setToolbar(props.options);
}
setTheme(props.theme || 'snow');
setValue(props.value);
}, [props]);
// 当内容变化时
const handleOnChange = useCallback(
contents => {
if (disableBold) {
setValue({
ops: contents.ops.map(x => {
x = {...x};
if (x && x.attributes && x.attributes.bold) {
x.attributes = { ...x.attributes };
delete x.attributes.bold;
if (!Object.keys(x.attributes).length) {
delete x.attributes;
}
}
return x;
})
});
} else {
setValue(contents);
}
onContentChagne && onContentChagne(contents);
}, [disableBold]
);
// 图片上传
const handleUploadImage = (files) => {
console.log('选择的图片信息', files);
}
// 显示图片
const handleShowUploadImage = (url) => {
// console.log('上传的图片url:', url);
showUploadImage && showUploadImage(url);
}
return (
<React.Fragment>
<ReactQuill
value={value}
style={props.style}
onChange={handleOnChange}
placeholder={`${placeholder}`}
options={options}
uploadImage={handleUploadImage}
showUploadImage={(url) => handleShowUploadImage(url)}
/>
</React.Fragment>
);
}
export default Wrapper;
// ReactDOM.render(<Wrapper />, document.querySelector('#root'));

@ -1,32 +0,0 @@
#quill-toolbar{
.quill-btn{
vertical-align: middle;
}
.quill_image{
display: inline-block;
position: relative;
vertical-align: middle;
width: 28px;
height: 24px;
overflow: hidden;
.image_input{
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
opacity: 0;
}
.ql-image{
position: relative;
left: 0;
top: 0;
}
}
}
.react_quill_area{
.ql-toolbar:not(:last-child) {
display: none;
}
}

@ -1,13 +0,0 @@
/*
* @Description: 导出 ReactQuill
* @Author: tangjiang
* @Github:
* @Date: 2019-12-09 09:08:24
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-16 11:37:13
*/
import ReactQuill from './ReactQuill';
import useQuill from './useQuill';
export default ReactQuill;
export { useQuill };

@ -1,27 +0,0 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-12 19:48:55
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-16 11:38:16
*/
import { useState, useEffect } from 'react';
import deepEqual from './deepEqual';
function useDeepEqual (input) {
const [value, setValue] = useState(input);
useEffect(() => {
if (!deepEqual(input, value)) {
setValue(input)
}
}, [input, value]);
return value;
}
export default useDeepEqual;

@ -1,148 +0,0 @@
/*
* @Description: 创建 reactQuill实例
* @Author: tangjiang
* @Github:
* @Date: 2019-12-09 09:31:42
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-17 20:42:05
*/
import Quill from 'quill'; // 导入quill
import { useState, useEffect, useMemo } from 'react';
import flatten from './flatten.js';
import useDeepEqualMemo from './useDeepEqualMemo';
import Katex from 'katex';
import ImageBlot from './ImageBlot';
import { fetchUploadImage } from '../../services/ojService.js';
import { getImageUrl } from 'educoder'
window.katex = Katex;
Quill.register(ImageBlot);
function useMountQuill ({
element,
options: passedOptions,
uploadImage,
showUploadImage,
imgAttrs = {} // 指定图片的宽高属性
}) {
// 是否引入 katex
const [katexLoaded, setKatexLoaded] = useState(Boolean(window.katex))
const [quill, setQuill] = useState(null);
const options = useDeepEqualMemo(passedOptions);
console.log('use mount quill: ', passedOptions);
// 判断options中是否包含公式
const requireKatex = useMemo(() => {
return flatten(options.modules.toolbar).includes('formula');
}, [options]);
// 加载katex
useEffect(() => {
if (!requireKatex) return;
if (katexLoaded) return;
const interval = setInterval(() => {
if (window.katex) {
setKatexLoaded(true);
clearInterval(interval);
}
});
return () => { // 定义回调清除定时器
clearInterval(interval);
}
}, [
setKatexLoaded,
katexLoaded,
requireKatex
]);
// 加载 quill
useEffect(() => {
if (!element) return;
if (requireKatex && !katexLoaded) {
element.innerHTML = `
<div style="color: #ddd">
Loading Katex...
</div>
`
}
// 清空内容
element.innerHTML = '';
console.log(element);
// 创建 quill 节点
const quillNode = document.createElement('div');
element.appendChild(quillNode); // 将quill节点追回到 element 元素中
const quill = new Quill(element, options);
setQuill(quill);
// 加载上传图片功能
if (typeof uploadImage === 'function') {
quill.getModule('toolbar').addHandler('image', (e) => {
// 创建type类型输入框加载本地图片
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = async (e) => {
const file = input.files[0]; // 获取文件信息
const formData = new FormData();
formData.append('file', file);
// const reader = new FileReader();
// reader.readAsDataURL(file);
// console.log('文件信息===>>', reader);
// reader.onload = function (e) {
// debugger;
// console.log('文件信息===>>', e.target.result);
// const image = new Image();
// image.src = e.target.result;
// image.onload = function () {
// // file.width =
// console.log(image.width, image.height);
// }
// }
const range = quill.getSelection(true);
let fileUrl = ''; // 保存上传成功后图片的url
// 上传文件
const result = await fetchUploadImage(formData);
// 获取上传图片的url
if (result.data && result.data.id) {
fileUrl = getImageUrl(`api/attachments/${result.data.id}`);
}
// 根据id获取文件路径
const { width, height } = imgAttrs;
// console.log('上传图片的url:', fileUrl);
if (fileUrl) {
quill.insertEmbed(range.index, 'image', {
url: fileUrl,
alt: '',
onClick: showUploadImage,
width,
height
});
}
}
});
}
return () => {
element.innerHTML = '';
}
}, [
element,
options,
requireKatex,
katexLoaded,
]);
return quill;
}
export default useMountQuill;

@ -1,60 +0,0 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-09 09:09:50
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-17 15:46:50
*/
import useQuillPlaceholder from './useQuillPlaceholder';
import useQuillValueSync from './useQuillValueSync';
import useQuillOnChange from './useQuillOnChange';
import useMountQuill from './useMountQuill';
import { useEffect } from 'react';
function useQuill ({
disallowColors,
placeholder,
uploadImage,
onChange,
options,
value,
element,
showUploadImage
}) {
// 获取 quill 实例
const quill = useMountQuill({
element,
options,
uploadImage,
showUploadImage
});
useEffect(() => {
if (disallowColors && quill) {
quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
delta.ops = delta.ops.map(op => {
if (op.attributes && op.attributes.color) {
const { color, ...attributes } = op.attributes;
return {
...op,
attributes
}
}
return op;
});
return delta;
});
}
}, [
disallowColors,
quill
]);
useQuillPlaceholder(quill, placeholder);
useQuillValueSync(quill, value);
useQuillOnChange(quill, onChange);
}
export default useQuill;

@ -1,33 +0,0 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-12 19:49:11
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-16 11:39:27
*/
import { useEffect } from 'react';
function useQuillOnChange (quill, onChange) {
useEffect(() => {
if (!quill) return;
if (typeof onChange !== 'function') return;
let handler;
quill.on(
'text-change',
(handler = () => {
onChange(quill.getContents()); // getContents: 检索编辑器内容
})
);
return () => {
quill.off('text-change', handler);
}
}, [quill, onChange]);
}
export default useQuillOnChange;

@ -1,22 +0,0 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-09 09:28:34
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-16 11:39:48
*/
import { useEffect } from 'react'
function useQuillPlaceholder (
quill,
placeholder
) {
useEffect(() => {
if (!quill || !quill.root) return;
quill.root.dataset.placeholder = placeholder;
}, [quill, placeholder]);
}
export default useQuillPlaceholder;

@ -1,31 +0,0 @@
import { useEffect, useState } from 'react'
import deepEqual from './deepEqual.js'
function useQuillValueSync(quill, value) {
const [selection, setSelection] = useState(null)
useEffect(() => {
if (!quill) return
const previous = quill.getContents()
const current = value
if (!deepEqual(previous, current)) {
setSelection(quill.getSelection())
if (typeof value === 'string') {
quill.clipboard.dangerouslyPasteHTML(value, 'api')
} else {
quill.setContents(value)
}
}
}, [quill, value, setSelection])
useEffect(() => {
if (quill && selection) {
quill.setSelection(selection)
setSelection(null)
}
}, [quill, selection, setSelection])
}
export default useQuillValueSync

@ -10,7 +10,7 @@ import { CNotificationHOC } from './common/CNotificationHOC'
import {ImageLayerOfCommentHOC} from '../page/layers/ImageLayerOfCommentHOC'
import "./css/Courses.css"
//引入对应跳转的组件
//里面有资源
const ListPageIndex = Loadable({
loader: () => import('./ListPageIndex'),
loading:Loading,
@ -508,12 +508,16 @@ class CoursesIndex extends Component{
}
></Route>
{/*视频列表*/}
<Route path="/courses/:coursesId/course_video/:videoId"
render={
(props) => (<ListPageIndex {...this.props} {...props} {...this.state} />)
}
></Route>
<Route path="/courses/:coursesId/course_videos"
render={
(props) => (<ListPageIndex {...this.props} {...props} {...this.state} />)
}
></Route>
></Route>
{/* 资源列表页 */}
<Route path="/courses/:coursesId/file/:Id" exact
render={
@ -960,4 +964,4 @@ class CoursesIndex extends Component{
}
}
export default withRouter(ImageLayerOfCommentHOC({imgSelector: '.imageLayerParent img, .imageLayerParent .imageTarget', parentSelector: '.newMain'}) (CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC(CoursesIndex) ))));
export default withRouter(ImageLayerOfCommentHOC({imgSelector: '.imageLayerParent img, .imageLayerParent .imageTarget', parentSelector: '.newMain'}) (CNotificationHOC() ( SnackbarHOC() ( TPMIndexHOC(CoursesIndex) ))));

@ -244,7 +244,11 @@ class ListPageIndex extends Component{
}
></Route>
{/*视频列表*/}
<Route path="/courses/:coursesId/course_video/:videoId"
render={
(props) => (<CourseVideo {...this.props} {...props} {...this.state} />)
}
></Route>
<Route path="/courses/:coursesId/course_videos"
render={
(props) => (<CourseVideo {...this.props} {...props} {...this.state} />)

@ -1,6 +1,6 @@
import React,{ Component } from "react";
import { WordsBtn } from 'educoder';
import {Tooltip,message} from 'antd';
import React, {Component} from "react";
import {WordsBtn} from 'educoder';
import {Tooltip, message} from 'antd';
import {Link} from 'react-router-dom';
import {getImageUrl} from 'educoder';
import axios from 'axios'
@ -10,25 +10,40 @@ import CoursesListType from '../coursesPublic/CoursesListType';
import Showoldfiles from "../coursesPublic/Showoldfiles";
import Modals from '../../modals/Modals';
class Fileslistitem extends Component{
constructor(props){
class Fileslistitem extends Component {
constructor(props) {
super(props);
this.state = {
}
this.state = {}
}
settingList=()=>{
let {discussMessage}=this.props
settingList = (bools) => {
let {discussMessage} = this.props
this.setState({
discussMessageid:discussMessage.id
discussMessageid: discussMessage.id
})
this.props.Settingtypes(discussMessage.id)
}
if (bools === true) {
this.props.Settingtypes(discussMessage.id)
} else {
this.props.Settingtypess(discussMessage.id,discussMessage.title,discussMessage.link)
}
showfiles=(list)=>{
if(this.props.checkIfLogin()===false){
}
//外链
showfiless = (url,id) => {
window.open(url)
let urls=`/files/${id}/update_visits.json`;
axios.post(urls,{
}).then((result)=>{
if(result.data.status===0){
this.props.Updateresourcepage()
}else{
this.props.showNotification(result.data.message);
}
})
}
showfiles = (list) => {
if (this.props.checkIfLogin() === false) {
this.props.showLoginDialog()
return
}
@ -43,21 +58,21 @@ class Fileslistitem extends Component{
// return
// }
if(list.is_history_file===false){
if (list.is_history_file === false) {
// this.props.DownloadFileA(list.title,list.url)
//window.location.href=list.url;
window.open(list.url, '_blank');
}else{
let {discussMessage,coursesId}=this.props
let file_id=discussMessage.id
let url="/files/"+file_id+"/histories.json"
axios.get(url,{
params:{
course_id:coursesId
} else {
let {discussMessage, coursesId} = this.props
let file_id = discussMessage.id
let url = "/files/" + file_id + "/histories.json"
axios.get(url, {
params: {
course_id: coursesId
},
}).then((result)=>{
}).then((result) => {
if(result.data.attachment_histories.length===0){
if (result.data.attachment_histories.length === 0) {
// if(result.data.is_pdf===true){
// this.props.ShowOnlinePdf(result.data.url)
// //预览pdf
@ -66,64 +81,66 @@ class Fileslistitem extends Component{
// }
// this.props.DownloadFileA(result.data.title,result.data.url)
window.open(list.url, '_blank');
}else{
} else {
this.setState({
Showoldfiles:true,
allfiles:result.data
Showoldfiles: true,
allfiles: result.data
})
}
}).catch((error)=>{
}).catch((error) => {
console.log(error)
})
}
}
closaoldfilesprops=()=>{
closaoldfilesprops = () => {
this.setState({
Showoldfiles:false,
Showoldfiles: false,
})
}
onDelete = (id) => {
this.setState({
Modalstype:true,
Modalstopval:"是否确认删除?",
ModalCancel:this.cancelmodel,
ModalSave:()=>this.savedelete(id),
Modalstype: true,
Modalstopval: "是否确认删除?",
ModalCancel: this.cancelmodel,
ModalSave: () => this.savedelete(id),
})
}
cancelmodel=()=>{
cancelmodel = () => {
this.setState({
Modalstype:false,
Loadtype:false,
Modalstopval:"",
ModalCancel:"",
ModalSave:"",
checkBoxValues:[],
Modalstype: false,
Loadtype: false,
Modalstopval: "",
ModalCancel: "",
ModalSave: "",
checkBoxValues: [],
})
}
savedelete=(id)=>{
savedelete = (id) => {
this.setState({
Modalstype:false,
Modalstype: false,
})
const cid = this.props.match.params.coursesId
const url = `/files/bulk_delete.json`;
axios.delete(url, { data: {
course_id:cid,
axios.delete(url, {
data: {
course_id: cid,
ids: [id],
}})
}
})
.then((response) => {
if (response.data.status == 0) {
//Modalstopval:response.data.message,
@ -132,11 +149,11 @@ class Fileslistitem extends Component{
this.setState({
// Modalstype:true,
// Modalstopval:"删除成功",
ModalsBottomval:"",
ModalsBottomval: "",
// ModalSave:this.cancelmodel,
// Loadtype:true,
checkBoxValues:[],
checkAllValue:false
checkBoxValues: [],
checkAllValue: false
})
this.props.showNotification("删除成功");
@ -147,28 +164,30 @@ class Fileslistitem extends Component{
});
}
eventStop = (event) =>{
eventStop = (event) => {
event.stopPropagation()
}
render(){
render() {
const { checkBox,
discussMessage,index
const {
checkBox,
discussMessage, index
} = this.props;
return(
let bools = discussMessage.link && discussMessage.link ? false : true;
return (
<div className="graduateTopicList boardsList">
{/*提示*/}
{this.state.Modalstype&&this.state.Modalstype===true?<Modals
{this.state.Modalstype && this.state.Modalstype === true ? <Modals
modalsType={this.state.Modalstype}
modalsTopval={this.state.Modalstopval}
modalCancel={this.state.ModalCancel}
modalSave={this.state.ModalSave}
modalsBottomval={this.state.ModalsBottomval}
loadtype={this.state.Loadtype}
/>:""}
/> : ""}
<Showoldfiles
{...this.props}
visible={this.state.Showoldfiles}
@ -213,67 +232,132 @@ class Fileslistitem extends Component{
margin-top:2px;
}
`}</style>
<div className="clearfix ds pr contentSection" style={{cursor : this.props.isAdmin ? "pointer" : "default"}} onClick={() => window.$(`.sourceitem${index} input`).click() }>
<h6 onClick={(event)=>this.eventStop(event)}>
<div className="clearfix ds pr contentSection" style={{cursor: this.props.isAdmin ? "pointer" : "default"}}
onClick={() => window.$(`.sourceitem${index} input`).click()}>
<h6 onClick={(event) => this.eventStop(event)}>
<span className={`sourceitem${index} fl mr12 mt3`}>
{checkBox}
</span>
{
this.props.isAdmin ? <a
// href={"/courses/" + coursesId + "/graduation/graduation_tasks/" + categoryid + "/" + taskid + "/list"}
onClick={()=>this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a> : ""
this.props.isAdmin ?
(bools === true ?
<a
// href={"/courses/" + coursesId + "/graduation/graduation_tasks/" + categoryid + "/" + taskid + "/list"}
onClick={() => this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a>
:
<a
// href={"/courses/" + coursesId + "/graduation/graduation_tasks/" + categoryid + "/" + taskid + "/list"}
onClick={() => this.showfiless(discussMessage.link,discussMessage.id)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a>
)
: ""
}
{
this.props.isStudent? <a
onClick={()=>this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a> :""
this.props.isStudent ?
(bools === true ?
<a
onClick={() => this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a>
:
<a
onClick={() => this.showfiless(discussMessage.link,discussMessage.id)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a>
)
: ""
}
{
this.props.isNotMember===true?
this.props.isNotMember === true ?
discussMessage.is_lock === true ?
<span className="fl mt3 font-16 font-bd color-dark maxwidth580 pointer" title={"私有属性,非课堂成员不能访问"}>{discussMessage.title}</span>
:<a
onClick={()=>this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a>:""
<span className="fl mt3 font-16 font-bd color-dark maxwidth580 pointer"
title={"私有属性,非课堂成员不能访问"}>{discussMessage.title}</span>
:
(bools === true ?
<a
onClick={() => this.showfiles(discussMessage)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a> :
<a
onClick={() =>this.showfiless(discussMessage.link,discussMessage.id)}
title={discussMessage.title}
className="fl mt3 font-16 font-bd color-dark maxwidth580">{discussMessage.title}</a>
)
: ""
}
{
discussMessage.is_lock === true ?
<Tooltip title={"私有属性,非课堂成员不能访问"} placement="bottom">
<Tooltip title={"私有属性,非课堂成员不能访问"} placement="bottom">
<i className="iconfont icon-guansuo color-grey-c ml10 font-16 fl mt4"></i>
</Tooltip>
:""
: ""
}
<style>
{
`
.fwlz{
width:40px;
height:20px;
border-radius:2px;
border:1px solid rgba(250,100,0,1);
font-size:12px;
font-family:MicrosoftYaHei;
color:rgba(250,100,0,1);
text-align: center;
margin-left: 15px;
}
`
}
</style>
{
discussMessage.link && discussMessage.link ?
<p className="fl mt3 fwlz " style={{
cursor:"auto"
}}>外链</p>
:
""
}
{discussMessage.is_publish===false?<CoursesListType typelist={["未发布"]} typesylename={""}/>:""}
{this.props.isAdmin?
<span className={"fr mt2"} onClick={(event)=>this.eventStop(event)}>
{discussMessage.is_publish === false ? <CoursesListType typelist={["未发布"]} typesylename={""}/> : ""}
{this.props.isAdmin ?
<span className={"fr mt2"} onClick={(event) => this.eventStop(event)}>
<WordsBtn style="blue" className="colorblue font-16 ml20 fr">
<a className="btn colorblue fontweight400"
onClick={()=>this.settingList()}>设置</a>
onClick={() => this.settingList(bools)}>设置</a>
</WordsBtn>
</span>:""}
</span> : ""}
{this.props.isStudent===true&&this.props.current_user.login===discussMessage.author.login?
<span className={"fr mt2"} onClick={(event)=>this.eventStop(event)}>
{this.props.isStudent === true && this.props.current_user.login === discussMessage.author.login ?
<span className={"fr mt2"} onClick={(event) => this.eventStop(event)}>
<WordsBtn style="blue" className="colorblue font-16 ml20 fr">
<a className="btn colorblue fontweight400"
onClick={()=>this.settingList()}>设置</a>
onClick={() => this.settingList(bools)}>设置</a>
</WordsBtn>
<WordsBtn style="blue" className="colorblue font-16 ml20 fr">
<a className="btn colorblue fontweight400"
onClick={()=>this.onDelete(discussMessage.id)}>删除</a>
onClick={() => this.onDelete(discussMessage.id)}>删除</a>
</WordsBtn>
</span>:""}
</span> : ""}
</h6>
<style>
{
@ -288,7 +372,6 @@ class Fileslistitem extends Component{
</style>
<style>
{
`
@ -315,28 +398,45 @@ class Fileslistitem extends Component{
{/*</p>}*/}
<p className={this.props.isAdmin===true?"color-grey panel-lightgrey mt8 fl ml30":"color-grey panel-lightgrey mt8 fl ml13"} style={{width:'100%'}}>
<p
className={this.props.isAdmin === true ? "color-grey panel-lightgrey mt8 fl ml30" : "color-grey panel-lightgrey mt8 fl ml13"}
style={{width: '100%'}}>
<span className="mr50">
<span className="mr15 color-dark">{discussMessage.author.name}</span>
<span className="mr15 color-grey9">大小 {discussMessage.filesize}</span>
<span className="mr15 color-grey9">下载 {discussMessage.downloads_count}</span>
{
bools ?
<span className="mr15 color-grey9">大小 {discussMessage.filesize}</span>
:
""
}
{
bools ?
<span className="mr15 color-grey9">下载 {discussMessage.downloads_count}</span>
:
<span className="mr15 color-grey9">点击次数{discussMessage.downloads_count}</span>
}
{/*<span className="mr15 color-grey9">引用 {discussMessage.quotes}</span>*/}
<span className="mr15 color-grey-c">
{/*{moment(discussMessage.publish_time).format('YYYY-MM-DD HH:mm:ss')}*/}
{/*{moment(discussMessage.publish_time).fromNow()}*/}
{ discussMessage.publish_time===null?"":
discussMessage.is_publish===true?"":"发布于"}
{ discussMessage.publish_time===null?"":discussMessage.is_publish===true?moment(discussMessage.publish_time).fromNow():moment(discussMessage.publish_time).format('YYYY-MM-DD HH:mm')}
{discussMessage.publish_time === null ? "" :
discussMessage.is_publish === true ? "" : "发布于"}
{discussMessage.publish_time === null ? "" : discussMessage.is_publish === true ? moment(discussMessage.publish_time).fromNow() : moment(discussMessage.publish_time).format('YYYY-MM-DD HH:mm')}
</span>
</span>
{discussMessage&&discussMessage.category_name===null?"":this.props.child===false?<div className="color-grey9 task-hide fr mr30" title={discussMessage&&discussMessage.category_name}
style={{"max-width":"268px"}}>所属目录{discussMessage&&discussMessage.category_name}
</div>:""}
{discussMessage && discussMessage.category_name === null ? "" : this.props.child === false ?
<div className="color-grey9 task-hide fr mr30" title={discussMessage && discussMessage.category_name}
style={{"max-width": "268px"}}>所属目录{discussMessage && discussMessage.category_name}
</div> : ""}
</p>
<p className={this.props.isAdmin===true?"color-grey panel-lightgrey mt8 fl ml30":"color-grey panel-lightgrey mt8 fl ml13"} style={{width:'94%'}}>
<p
className={this.props.isAdmin === true ? "color-grey panel-lightgrey mt8 fl ml30" : "color-grey panel-lightgrey mt8 fl ml13"}
style={{width: '94%'}}>
<style>
{
`
@ -349,7 +449,8 @@ class Fileslistitem extends Component{
`
}
</style>
<span className="color-dark isspans">资源描述 :{discussMessage.description===null?"暂无描述":discussMessage.description}</span>
<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">*/}
@ -365,4 +466,5 @@ class Fileslistitem extends Component{
)
}
}
export default Fileslistitem;

@ -6,6 +6,8 @@ import Modals from '../../modals/Modals';
import Sendtofilesmodal from "../coursesPublic/SendToFilesModal";
import Selectresource from "../coursesPublic/SelectResource";
import Sendresource from "../coursesPublic/sendResource";
import SendResources from "../coursesPublic/sendResources";
import Selectsetting from "../coursesPublic/SelectSetting";
import HomeworkModal from "../coursesPublic/HomeworkModal";
import Fileslistitem from './Fileslistitem';
@ -34,12 +36,16 @@ class Fileslists extends Component{
name:"",
sendTotype:false,
Accessoryvisible:false,
discussMessageid:undefined,
Addanexternallink:false,
Exterchainname:"添加外链",
discussMessageid:undefined,
course_modules:undefined,
has_course_groups:false,
course_is_public:undefined,
isSpin:false,
course_second_categories:[]
course_second_categories:[],
title: "",
link: ""
}
}
@ -229,16 +235,15 @@ class Fileslists extends Component{
filesId:list.id,
name:list.name,
course_is_public:result.data.data.course_is_public,
isSpin:false,
page:page
})
}
}
}else{
this.setState({
}
this.setState({
isSpin:false
})
}
}).catch((error)=>{
console.log(error)
this.setState({
@ -570,8 +575,48 @@ class Fileslists extends Component{
})
}
//添加外链资源设置
sendResourcessls = (ints,bool) => {
if(bool===true){
if(ints===1){
this.setState({
Addanexternallink:true,
Exterchainname:"添加外链"
})
}else{
this.setState({
Addanexternallink:true,
Exterchainname:"资源设置"
})
}
}else{
this.setState({
Addanexternallink:false,
title: "",
link: "",
discussMessageid:null,
})
if(ints===1){
this.Updateresourcepage();
}
}
}
Updateresourcepage=()=>{
let{pagesize,tagname,searchValue,page,sort,sorttype,coursesecondcategoryid}=this.state;
this.getfileslist(pagesize,page,tagname,searchValue,sort,sorttype,coursesecondcategoryid);
}
Cancelvisible=()=>{
Cancelvisible=()=>{
this.setState({
Accessoryvisible:false,
@ -587,6 +632,17 @@ class Fileslists extends Component{
})
}
Settingtypess=(id,title,link)=>{
debugger
this.setState({
Addanexternallink:true,
Exterchainname:"资源设置",
discussMessageid:id,
title: title,
link: link
})
}
moveTos=(id)=>{
let {checkBoxValues} = this.state;
@ -729,7 +785,11 @@ class Fileslists extends Component{
course_is_public,
filesId,
child,
sort
sort,
Addanexternallink,
Exterchainname,
title,
link,
} = this.state;
let category_id= this.props.match.params.category_id;
@ -781,7 +841,7 @@ class Fileslists extends Component{
loadtype={Loadtype}
/>:""}
{
shixunmodal===true||Accessoryvisible===true||Settingtype===true?<style>
shixunmodal===true||Accessoryvisible===true||Settingtype===true||Addanexternallink===true?<style>
{
`
body {
@ -821,6 +881,25 @@ class Fileslists extends Component{
has_course_groups={this.state.has_course_groups}
attachmentId={this.state.coursesecondcategoryid}
/>:""}
{/*添加外链*/}
{Addanexternallink&&Addanexternallink===true?<SendResources
{...this.props}
{...this.state}
title={title}
link={link}
modalname={Exterchainname}
visible={Addanexternallink}
discussMessageid={discussMessageid}
Cancelname={"取消"}
Savesname={"确认"}
Cancel={()=>this.sendResourcessls(2,false)}
categoryid={category_id}
setupdate={(ints,bool)=>this.sendResourcessls(ints,bool)}
has_course_groups={this.state.has_course_groups}
course_id={this.props.match.params.coursesId}
attachmentId={this.state.coursesecondcategoryid}
/>:""}
{/*设置资源*/}
{Settingtype&&Settingtype===true?<Selectsetting
{...this.props}
@ -858,8 +937,10 @@ class Fileslists extends Component{
{this.props.isAdmin()?parseInt(this.props.match.params.main_id)!=parseInt(this.props.coursesids)?<WordsBtn style="blue" onClick={()=>this.editDir(name)} className={"mr30 font-16"}>目录重命名</WordsBtn>:"":""}
{this.props.isAdmin()||this.props.isStudent() ? this.props.user&&this.props.user.main_site===true? <WordsBtn style="blue" className="mr30 font-16" onClick={()=>this.addResource()}>选用资源</WordsBtn>:"":""}
{this.props.isAdmin()||this.props.isStudent() ? <WordsBtn style="blue" className=" font-16" onClick={()=>this.sendResources()}>上传资源</WordsBtn>:""}
</React.Fragment>
{this.props.isAdmin()||this.props.isStudent() ? <WordsBtn style="blue" className="mr30 font-16" onClick={()=>this.sendResources()}>上传资源</WordsBtn>:""}
{this.props.isAdmin()||this.props.isStudent() ? <WordsBtn style="blue" className=" font-16" onClick={()=>this.sendResourcessls(1,true)}>添加外链</WordsBtn>:""}
</React.Fragment>
}
secondRowLeft={
@ -997,11 +1078,13 @@ class Fileslists extends Component{
{...this.props}
{...this.state}
discussMessage={item}
Updateresourcepage={()=>this.Updateresourcepage()}
isAdmin={this.props.isAdmin()}
isStudent={this.props.isStudent()}
isNotMember={this.props.isNotMember()}
checkBox={this.props.isAdmin()?<Checkbox value={item.id} key={item.id}></Checkbox>:""}
Settingtypes={(id)=>this.Settingtypes(id)}
Settingtypess={(id,t,l)=>this.Settingtypess(id,t,l)}
coursesId={this.props.match.params.coursesId}
updatafiledfun={()=>this.updatafiled()}
index={index}

@ -0,0 +1,128 @@
import React,{ Component } from "react";
import { Radio , Modal } from 'antd';
import './video.css';
import axios from 'axios';
class MoveBox extends Component{
constructor(props){
super(props);
this.state={
data:undefined,
selectSubId:undefined
}
}
componentDidUpdate=(prevProps)=>{
if(this.props.id && this.props.visible && this.props.id !== prevProps.id){
this.getSubList(this.props.mainId);
}
}
getSubList=(id)=>{
const url = `/course_modules/${id}.json`;
axios.get(url).then(result=>{
if(result){
let list = result.data.course_module && result.data.course_module.course_second_categories;
let defaultId = list.length>0 ? list[0].id : undefined;
this.setState({
data:result.data.course_module,
selectSubId:defaultId
})
}
}).catch(error=>{
console.log(error);
})
}
cancelMove=()=>{
const { setMoveVisible } = this.props;
setMoveVisible && setMoveVisible(false);
}
// 选择子目录
selectSub=(e)=>{
this.setState({
selectSubId:e.target.value
})
}
handleSubmit=()=>{
const CourseId = this.props.match.params.coursesId;
const { id } = this.props;
const { selectSubId } = this.state;
const url = `/courses/${CourseId}/move_to_category.json`;
axios.post(url,{
video_ids:[id],
new_category_id:selectSubId
}).then(result=>{
if(result){
const { setMoveVisible , successFunc , updataleftNavfun} = this.props;
updataleftNavfun && updataleftNavfun();
setMoveVisible && setMoveVisible(false);
successFunc && successFunc();
}
}).catch(error=>{
console.log(error);
})
}
render(){
const { visible , id } = this.props;
const { data , selectSubId } = this.state;
let list = data && data.course_second_categories && data.course_second_categories.length>0?data.course_second_categories:undefined;
return(
<Modal
visible={visible}
width="560px"
title={'移动到'}
footer={null}
closable={false}
>
<div>
<style>
{
`
.ant-radio-group.ant-radio-group-outline{
display: flex;
flex-direction: column;
}
.ant-radio-wrapper{
margin-bottom:5px;
display:flex;
}
.ant-radio{
margin-top:2px;
}
.ant-radio-group.ant-radio-group-outline span:last-child{
max-height: 450px;
display: block;
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
`
}
</style>
<Radio.Group onChange={this.selectSub} value={selectSubId}>
{
list && list.map((item,key)=>{
return(
<Radio value={item.id} key={item.id}>
{item.name}
</Radio>
)
})
}
</Radio.Group>
<div className="clearfix mt30 edu-txt-center">
<a onClick={this.cancelMove} className="task-btn mr30">取消</a>
<a type="submit" onClick={this.handleSubmit} className="task-btn task-btn-orange">确定</a>
</div>
</div>
</Modal>
)
}
}
export default MoveBox;

@ -5,7 +5,8 @@ import { NoneData, ActionBtn } from 'educoder';
import VideoUploadList from '../../user/usersInfo/video/VideoUploadList';
import VideoInReviewItem from '../../user/usersInfo/video/VideoInReviewItem';
import HeadlessModal from '../../user/usersInfo/common/HeadlessModal';
import EditVideoModal from '../../user/usersInfo/video/EditVideoModal'
import EditVideoModal from '../../user/usersInfo/video/EditVideoModal';
import MoveBox from './MoveBox';
import ClipboardJS from 'clipboard'
import VideoPanel from './video-play'
@ -28,7 +29,10 @@ class Video extends Component {
videoId: undefined,
videoVisible: false,
visible: false
visible: false,
moveVisible:false,
moveVideoId:undefined
}
}
@ -69,7 +73,7 @@ class Video extends Component {
// 编辑成功后回调的方法
editSuccess = () => {
this.props.showNotification("视频名称修改成功!");
this.props.showNotification("视频信息修改成功!");
const { listFunc, page } = this.props;
listFunc && listFunc(page);
}
@ -77,7 +81,8 @@ class Video extends Component {
onEditVideo = (item) => {
let videoId = {
videoId: item.id,
title: item.title
title: item.title,
link:item.link
}
this.setState({
videoId,
@ -142,7 +147,8 @@ class Video extends Component {
const url = `/courses/${CourseId}/delete_course_video.json`;
axios.delete(url, {
params: {
video_id: item.id
video_id: item.id,
is_link:item.link ? true : undefined
}
}).then(result => {
if (result) {
@ -161,9 +167,25 @@ class Video extends Component {
});
}
// 移动到
moveVideo=(id)=>{
this.setState({
moveVisible:true,
moveVideoId:id
})
}
setMoveVisible=(flag)=>{
this.setState({
moveVisible:flag,
moveVideoId:undefined
})
}
render() {
const { visible, videoVisible, videoId } = this.state;
const { visible, videoVisible, videoId , moveVisible , moveVideoId } = this.state;
const CourseId = this.props.match.params.coursesId;
const VID=this.props.match.params.videoId;
const login = this.props.user && this.props.user.login;
const _inputValue = videoId && this.getCopyText(videoId.file_url, videoId.cover_url);
@ -171,13 +193,21 @@ class Video extends Component {
const { videos, upload, uploadVideo, videoData, changePage, pageSize, page } = this.props;
const operation = admin || business || (is_teacher && this.props.checkIfProfessionalCertification())
const operation = admin || business;
return (
<div>
<EditVideoModal {...this.props} visible={visible} setVisible={this.setVisible}
editSuccess={this.editSuccess}
{...videoId} CourseUser={login}
></EditVideoModal>
<MoveBox
{...this.props}
visible={moveVisible}
mainId={videoData && videoData.course_module_id}
setMoveVisible={(flag)=>this.setMoveVisible(flag)}
successFunc={()=>uploadVideo()}
id={moveVideoId}
></MoveBox>
<HeadlessModal
visible={videoVisible}
setVisible={this.setVideoVisible}
@ -196,7 +226,7 @@ class Video extends Component {
<div className="videoPanel">
{
upload ?
<VideoUploadList {...this.props} flag={true} CourseId={CourseId} CourseUser={login} successFunc={() => uploadVideo()}></VideoUploadList>
<VideoUploadList {...this.props} flag={true} CourseId={CourseId} videoId={VID} CourseUser={login} successFunc={() => uploadVideo()}></VideoUploadList>
:
<React.Fragment>
{
@ -215,8 +245,9 @@ class Video extends Component {
onEditVideo={this.onEditVideo}
onMaskClick={this.onMaskClick}
getCopyText={this.getCopyText}
operation={operation}
operation={operation || item.user_id === user_id}
deleteVideo={(admin || item.user_id === user_id) ? this.deleteVideo : undefined}
moveVideo={videoData && videoData.has_category && (operation || item.user_id === user_id) ? ()=>this.moveVideo(item.id):undefined}
>
</VideoInReviewItem>
)

@ -1,13 +1,13 @@
import React,{ Component } from "react";
import { WordsBtn,on, trigger ,publicSearchs} from 'educoder';
import { Menu, Spin } from 'antd';
import { WordsBtn } from 'educoder';
import axios from 'axios';
import Videos from './Video';
import Lives from './Live';
import LivesNew from './LiveNew';
import VideoLink from './VideoLink';
import './video.css';
import '../css/Courses.css';
@ -28,6 +28,7 @@ class VideoIndex extends Component{
upload:false,
videos:undefined,
videoData:undefined,
otherLinkVisible:false,
type:"video",
isSpining:false,
@ -37,6 +38,7 @@ class VideoIndex extends Component{
liveId:undefined,
liveVisible:false
}
}
@ -69,6 +71,15 @@ class VideoIndex extends Component{
this.checkType("video",page);
}
}
componentDidUpdate = (prevProps) => {
if(this.props.match.params.videoId !== prevProps.match.params.videoId ){
this.setState({
upload:false
})
const { page } = this.state;
this.checkType("video",page);
}
}
// 获取直播列表
getLiveList=(page)=>{
const CourseId=this.props.match.params.coursesId;
@ -93,12 +104,13 @@ class VideoIndex extends Component{
// 获取视频列表
getList=(page)=>{
const CourseId=this.props.match.params.coursesId;
const fetchUrl = `/courses/${CourseId}/course_videos.json`;
const { coursesId , videoId }=this.props.match.params;
const fetchUrl = `/courses/${coursesId}/course_videos.json`;
axios.get(fetchUrl, {
params: {
page,
limit: PAGE_SIZE,
category_id:videoId
}
})
.then((response) => {
@ -141,6 +153,7 @@ class VideoIndex extends Component{
this.setVisible(true);
}
uploadVideo=(upload)=>{
this.setState({
upload,
isSpining:true
@ -200,12 +213,45 @@ class VideoIndex extends Component{
})
this.setliveVisibel(true);
}
// 新增目录
addDir=()=>{
let {videoData}=this.state;
trigger('videoAdd', parseInt(videoData.course_module_id));
}
// 目录重命名
editDir=(name,id)=>{
let data={id,name,update:this.getList}
trigger('editVideo',data);
}
// 增加外链
setLinkeVisible=(flag,refresh)=>{
this.setState({
otherLinkVisible:flag
})
if(refresh){
const { page } = this.state;
this.getList(page);
}
}
render(){
const { videos , upload , videoData , type , liveData , lives , page , liveVisible , isSpining , liveId } = this.state;
const { videos , upload , videoData , type , liveData , lives , page , liveVisible , isSpining , liveId , otherLinkVisible } = this.state;
const { admin , is_teacher , business } = this.props.user;
// console.log("p",this.props);
const { coursesId , videoId }=this.props.match.params;
const {course_identity} = this.props.coursedata;
const flag = parseInt(course_identity) < 4;
const newOperation = flag;
const new_upload = flag && (is_teacher && this.props.checkIfProfessionalCertification());
return(
<React.Fragment>
<VideoLink
coursesId={coursesId}
videoId={videoId}
visible={otherLinkVisible}
notification={this.props.showNotification}
setVisible={this.setLinkeVisible}
></VideoLink>
<LivesNew
visible={liveVisible}
liveId={liveId}
@ -235,30 +281,49 @@ class VideoIndex extends Component{
<div className="edu-back-white" style={{marginBottom:"1px"}}>
<div className="clearfix pl30 pr30 menuDiv">
<div className="task_menu_ul fl">
<Menu mode="horizontal" selectedKeys={[type]} onClick={this.changeType}>
<Menu.Item key="video">视频</Menu.Item>
<Menu.Item key="live">直播</Menu.Item>
</Menu>
</div>
{
(admin || is_teacher || business) &&
<li className="fr mt18">
{
type === "video" ?
<React.Fragment>
videoData && videoData.category_name && type === "video" ?
<span className="font-18 fl color-dark-21 mt20 mb20">{videoData.category_name}</span>
:
<div className="task_menu_ul fl mt2" style={{width:"400px"}}>
<Menu mode="horizontal" selectedKeys={[type]} onClick={this.changeType}>
<Menu.Item key="video">视频</Menu.Item>
<Menu.Item key="live">直播</Menu.Item>
</Menu>
</div>
}
<li className="fr mt20 mb20">
{
type === "video" ?
<React.Fragment>
{
newOperation ?
<span>
{
upload ?
<WordsBtn style="grey" className="font-16" onClick={()=>this.uploadVideo(false)}>取消</WordsBtn>
videoId ?
<WordsBtn style="blue" onClick={()=>this.editDir(videoData && videoData.category_name,videoId)} className={"mr30 font-16"}>目录重命名</WordsBtn>
:
<WordsBtn style="blue" className="font-16" onClick={this.toUpload}>上传视频</WordsBtn>
<WordsBtn style="blue" className="mr30 font-16" onClick={this.addDir}>新建目录</WordsBtn>
}
</React.Fragment>
:
<WordsBtn style="blue" className="font-16 ml30" onClick={this.liveSetting}>添加直播</WordsBtn>
}
</li>
}
<WordsBtn style="blue" className="mr30 font-16" onClick={()=>this.setLinkeVisible(true)}>增加外链</WordsBtn>
</span>:""
}
{
new_upload ?
<span>
{
upload ?
<WordsBtn style="grey" className="font-16" onClick={()=>this.uploadVideo(false)}>取消</WordsBtn>
:
<WordsBtn style="blue" className="font-16" onClick={this.toUpload}>上传视频</WordsBtn>
}
</span>:""
}
</React.Fragment>
:
<WordsBtn style="blue" className="font-16 ml30" onClick={this.liveSetting}>添加直播</WordsBtn>
}
</li>
</div>
</div>
<Spin spinning={isSpining}>

@ -0,0 +1,96 @@
import React,{ Component } from "react";
import { Modal , Form , Input , Spin , Select , AutoComplete , DatePicker , InputNumber } from 'antd';
import axios from 'axios';
class VideoLink extends Component{
componentDidUpdate=(prevProps)=>{
if(prevProps.visible !== this.props.visible){
this.props.form.setFieldsValue({
name:undefined,
link:undefined
})
}
}
cancelNew=()=>{
const { setVisible } = this.props;
setVisible && setVisible(false);
}
validateDesc= (rule, value, callback) => {
if(!value){
callback();
}
if (value.length > 60) {
callback("视频名称不能超过60个字");
}else{
callback();
}
}
// 提交
handleSubmit=()=>{
this.props.form.validateFields((err, values) => {
if(!err){
const { coursesId , videoId } = this.props;
const url = `/courses/${coursesId}/course_videos.json`;
axios.post(url,{
...values,
category_id:videoId
}).then(result=>{
if(result){
const { notification , setVisible } = this.props;
notification && notification('视频外链新增成功!');
setVisible && setVisible(false,true);
}
}).catch(error=>{
console.log(error);
})
}
})
}
render(){
const {getFieldDecorator} = this.props.form;
const { visible } = this.props;
const layout = {
labelCol: { span: 5 },
wrapperCol: { span: 19 },
}
return(
<Modal
visible={visible}
width="560px"
title={'添加外链'}
footer={null}
closable={false}
centered={true}
>
<div className="task-popup-content">
<Form onSubmit={this.handleSubmit} {...layout}>
<Form.Item label={`视频名称:`}>
{getFieldDecorator('name', {
rules: [{required: true, message: "请输入名称"},{
validator: this.validateDesc,
}],
})(
<Input placeholder="请输入名称最大限制60个字符" />
)}
</Form.Item>
<Form.Item label={`链接地址:`}>
{getFieldDecorator('link', {
rules: [{required: true, message: "请输入链接地址"}],
})(
<Input placeholder="请输入链接地址" />
)}
</Form.Item>
<div className="clearfix mt30 edu-txt-center">
<a onClick={this.cancelNew} className="task-btn mr30">取消</a>
<a type="submit" onClick={this.handleSubmit} className="task-btn task-btn-orange">确定</a>
</div>
</Form>
</div>
</Modal>
)
}
}
const WrappedVideoLink = Form.create({name: 'VideoLink'})(VideoLink);
export default WrappedVideoLink;

@ -61,6 +61,8 @@ class Coursesleftnav extends Component{
sandiantypes:undefined,
antIcon:false,
chapterupdate:false,
successFunc:undefined
}
}
@ -138,61 +140,68 @@ class Coursesleftnav extends Component{
off('shixun_homeworkadd',this.addshixunchild)
off('editshixunname',this.editshixunchild)
off('editshixunmainname',this.editshixunmainname)
off('videoAdd',this.addVideo)
off('editVideo',this.editVideo)
}
addshixunchild=(e, data)=>{
this.Navmodalnames(e,1,"shixun_homework",data)
this.Navmodalnames(e,1,"shixun_homework",data);
}
editshixunchild=(e, data)=>{
this.Navmodalnames(e,4,"editSecondname",data.id,data.name)
this.Navmodalnames(e,4,"editSecondname",data.id,data.name);
}
editshixunmainname=(e, data)=>{
this.Navmodalnames(e,3,"editname",data.id,data.name)
this.Navmodalnames(e,3,"editname",data.id,data.name);
}
boardAddListener = (e, data) => {
this.Navmodalnames(e,6,"board", data)
this.Navmodalnames(e,6,"board", data);
}
addVideo=(e,id)=>{
this.Navmodalnames(e,1,"video",id);
}
editVideo=(e,data)=>{
this.setState({
successFunc:data.update
})
this.Navmodalnames(e,4,"editSecondname",data.id,data.name);
}
boardRenameListener = (e, data) => {
this.Navmodalnames(e,7,"editSecondname", data.category_id, data.category_name)
this.Navmodalnames(e,7,"editSecondname", data.category_id, data.category_name);
}
groupAddListener = (e, data) => {
this.Navmodalnames(e,2,"course_group", data)
this.Navmodalnames(e,2,"course_group", data);
}
groupRenameListener = (e, data) => {
this.Navmodalnames(e,5,"editSecondname", data.id, data.name)
this.Navmodalnames(e,5,"editSecondname", data.id, data.name);
}
attachmentAddlog=(e,data)=>{
this.Navmodalnames(e,1,"attachment",data)
this.Navmodalnames(e,1,"attachment",data);
}
flieseditDir=(e, data)=>{
this.Navmodalnames(e,4,"editSecondname",data.id,data.name)
this.Navmodalnames(e,4,"editSecondname",data.id,data.name);
}
componentDidMount() {
this.setState({
url:this.props.match.url
})
on('boardAdd', this.boardAddListener)
on('boardRename', this.boardRenameListener)
on('groupAdd', this.groupAddListener)
on('groupRename', this.groupRenameListener)
on('attachmentAddlog', this.attachmentAddlog)
on('flieseditDir', this.flieseditDir)
on('shixun_homeworkadd',this.addshixunchild)
on('editshixunname',this.editshixunchild)
on('editshixunmainname',this.editshixunmainname)
on('boardAdd', this.boardAddListener);
on('boardRename', this.boardRenameListener);
on('groupAdd', this.groupAddListener);
on('groupRename', this.groupRenameListener);
on('attachmentAddlog', this.attachmentAddlog);
on('flieseditDir', this.flieseditDir);
on('shixun_homeworkadd',this.addshixunchild);
on('editshixunname',this.editshixunchild);
on('editshixunmainname',this.editshixunmainname);
on('videoAdd',this.addVideo);
on('editVideo',this.editVideo)
// this.props.updataleftNavfun();
// this.props.getleftNavid && this.props.getleftNavid("shixun_homework");
// const position =parseInt(this.props.match.params.position);
let courstype=this.props.match.url;
courstype = courstype.split('/');
courstype=courstype[3];
courstype = courstype.split('/');
// console.log(courstype)
courstype=courstype[3];
const query =this.props.location.search;
@ -520,7 +529,9 @@ class Coursesleftnav extends Component{
// loadtype:true,
// NavmodalValue:""
// })
navidtype=true
navidtype=true;
const { successFunc } = this.state;
successFunc && successFunc(1);
}
saveNavmodapost=(url,value,positiontype,coursesId)=>{
@ -658,32 +669,28 @@ class Coursesleftnav extends Component{
updatadeleteSecondary=(url)=>{
this.props.updataleftNavfun();
// this.setState({
// ModalsType:true,
// Modalstopval:"删除成功",
// loadtype:true,
// })
// notification.open({
// message: "删除成功",
// });
// this.props.history.replace(url);
window.location.href = url;
this.setState({
ModalsType:false,
Modalstopval:"",
loadtype:false,
})
notification.open({
message: "删除成功",
});
this.props.history.replace(url);
// window.location.href = url;
}
deletenavchilds=(url,mainurl)=>{
this.setState({
antIcon:true
})
console.log(this.props);
this.setState({
antIcon:true
})
axios.delete(url).then((result)=>{
if(result.data.status===0){
if(mainurl===undefined){
this.updatadeleteSecondary(result.data.right_url)
}else{
this.updatadeleteSecondary(mainurl)
}
this.updatadeleteSecondary(mainurl || result.data.right_url);
}
}).catch((error)=>{
console.log(error)
@ -698,8 +705,8 @@ class Coursesleftnav extends Component{
ModalsType:true,
Modalstopval:"该目录下的内容将被移动到父目录,",
ModalsBottomval:"是否确认删除?",
ModalSave:()=>this.deletenavchilds(url),
ModalSave:()=>this.deletenavchilds(url,mainurl),
loadtype:false
})
}else if(type===2){
@ -709,7 +716,7 @@ class Coursesleftnav extends Component{
Modalstopval:"该分班的学生将被移动到“未分班”,",
ModalsBottomval:"是否确认删除?",
ModalSave:()=>this.deletenavchilds(url),
loadtype:false
})
}else if(type===3){
let url="/boards/"+id+".json"
@ -718,7 +725,7 @@ class Coursesleftnav extends Component{
Modalstopval:"该目录下的内容将被移动到父目录,",
ModalsBottomval:"是否确认删除?",
ModalSave:()=>this.deletenavchilds(url,mainurl),
loadtype:false
})
}
@ -838,6 +845,8 @@ class Coursesleftnav extends Component{
{item.type==="shixun_homework"?<div onClick={e=>this.Navmodalnames(e,1,"shixun_homework",item.id)}>新建目录</div>:""}
{/*资源*/}
{item.type==="attachment"?<div onClick={e=>this.Navmodalnames(e,1,"attachment",item.id)}>新建目录</div>:""}
{/* 视频 */}
{item.type==="video"?<div onClick={e=>this.Navmodalnames(e,1,"video",item.id)}>新建目录</div>:""}
{/*毕业设计*/}
{/*{item.type==="graduation"?<div onClick={()=>this.Navmodalnames(1,"attachment",item.id)}>添加目录</div>:""}*/}
{/*讨论区*/}
@ -872,6 +881,9 @@ class Coursesleftnav extends Component{
{/*讨论区*/}
{item.type==="board"?<div onClick={e=>this.Navmodalnames(e,7,"editSecondname",iem.category_id,iem.category_name)}>重命名</div>:""}
{item.type==="board"?<div onClick={e=>this.deleteSecondary(e,3,iem.category_id,item.category_url)}>删除</div>:""}
{/*视频*/}
{item.type==="video"?<div onClick={e=>this.Navmodalnames(e,4,"editSecondname",iem.category_id,iem.category_name)}>重命名</div>:""}
{item.type==="video"?<div onClick={e=>this.deleteSecondary(e,1,iem.category_id,item.category_url)}>删除</div>:""}
</div>)
};

@ -0,0 +1,585 @@
import React,{ Component } from "react";
import { Modal,Checkbox,Upload,Button,Icon,message,DatePicker,Select,Tooltip,Radio,Input} from "antd";
import axios from 'axios';
import Modals from '../../modals/Modals';
import {getUploadActionUrl,handleDateString,appendFileSizeToUploadFileAll} from 'educoder';
import locale from 'antd/lib/date-picker/locale/zh_CN';
import moment from 'moment';
const CheckboxGroup = Checkbox.Group;
const Option = Select.Option;
function range(start, end) {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
}
function disabledDateTime() {
return {
// disabledHours: () => range(0, 24).splice(4, 20),
disabledMinutes: () => range(1, 30).concat(range(31, 60)),
// disabledSeconds: () => [0, 60],
};
}
function disabledDate(current) {
return current && current < moment().endOf('day').subtract(1, 'days');
}
const dateFormat="YYYY-MM-DD HH:mm";
class sendResources extends Component{
constructor(props){
super(props);
this.state={
group_ids:[],
fileList:[],
Modalstype:false,
Modalstopval:"",
ModalCancel:"",
ModalSave:"",
fileListtype:false,
loadtype:false,
is_public:false,
datatime:undefined,
resourcesname:"",
resourceurl:"",
addonAfteronelens3:0,
// moment(new Date()).format('YYYY-MM-DD HH:mm:ss'),
course_group_publish_times:[
{
course_group_id : undefined,
publish_time :""
}],
course_groups:undefined,
course_groups_count:undefined,
Radiovalue:0,
Radiovaluetype:false,
resourceurlbool:false,
resourcesnamebool:false,
}
}
componentDidMount() {
let {discussMessageid} =this.props;
try {
this.setState({
resourcesname:this.props.title,
resourceurl:this.props.link,
addonAfteronelens3:this.props.title.length,
})
}catch (e) {
}
console.log(discussMessageid);
try {
if(discussMessageid){
this.getalldata();
}
}catch (e) {
}
}
getalldata=()=>{
let {discussMessageid} =this.props;
let course_id=this.props.course_id;
let url="/files/"+discussMessageid+".json";
axios.get(url, {
params:{
course_id:course_id,
}
})
.then((response) => {
if(response.status===200){
this.setState({
datalist:response.data,
description: response.data.description,
is_public:response.data.is_public,
datatime:response.data.publish_time,
Radiovalue:response.data.delay_publish==false?0:1,
//attachment_histories:response.data.attachment_histories
})
}
})
.catch(function (error) {
console.log(error);
});
}
//勾选实训
shixunhomeworkedit=(list)=>{
this.setState({
group_ids:list
})
}
// 附件相关 START
handleChange = (info) => {
console.log(info)
if (info.file.status === 'uploading' || info.file.status === 'done' || info.file.status === 'removed') {
let fileList = info.fileList;
if (info.file.status != "removed") {
this.setState({
fileList: appendFileSizeToUploadFileAll(fileList),
fileListtype: true
});
} else {
this.setState({
fileList: appendFileSizeToUploadFileAll(fileList),
});
}
}
}
onAttachmentRemove = (file) => {
if(!file.percent || file.percent == 100){
const url = `/attachments/${file.response ? file.response.id : file.uid}.json`
axios.delete(url, {
})
.then((response) => {
if (response.data) {
const { status } = response.data;
if (status == 0) {
this.setState({
fileListtype:false,
fileList:[]
})
}
}
})
.catch(function (error) {
console.log(error);
});
this.setState({
fileListtype:false,
})
}else{
this.setState({
fileListtype:false,
fileList:[]
})
}
}
ModalCancelModalCancel=()=>{
this.setState({
Modalstype:false,
Modalstopval:"",
ModalSave:this.ModalCancelModalCancel,
loadtype:false
})
this.props.Cancel()
}
Saves=()=>{
let {resourcesname,resourceurl,description,is_public,datatime,Radiovalue} =this.state;
const reg = /^[\s\S]*.*[^\s][\s\S]*$/;
if (!reg.test(resourcesname)) {
this.setState({
resourcesnamebool:true
})
return;
}
if (!reg.test(resourceurl)) {
this.setState({
resourceurlbool:true
})
return;
}
if(this.state.Radiovalue===1){
if(datatime===undefined||datatime===null||datatime=== ""){
this.setState({
Radiovaluetype:true
})
return
}else{
this.setState({
Radiovaluetype:false
})
}
}
if(description===undefined){
}else if(description.length>100){
this.setState({
descriptiontype:true
})
return
}
if(this.props.Exterchainname==="资源设置"){
//设置
let coursesId=this.props.match.params.coursesId;
let attachmentId=this.props.attachmentId;
let url="/files/"+this.props.discussMessageid+".json";
axios.put(url,{
name:resourcesname,
link:resourceurl,
course_id:coursesId,
course_second_category_id:this.props.coursesidtype===undefined||this.props.coursesidtype==="node"?0:attachmentId,
is_public:is_public,
publish_time:Radiovalue===1?datatime===undefined? undefined:datatime:undefined,
description:description,
delay_publish:Radiovalue,
}).then((result)=>{
if(result.data.status===0){
this.ModalCancelModalCancel();
this.props.updataleftNavfun();
this.props.showNotification("设置资源成功");
this.props.setupdate(1,false)
}else{
this.props.showNotification(result.data.message);
}
})
}else{
let coursesId=this.props.match.params.coursesId;
let attachmentId=this.props.attachmentId;
let url="/files/upload.json";
axios.post(url,{
name:resourcesname,
link:resourceurl,
course_id:coursesId,
course_second_category_id:this.props.coursesidtype===undefined||this.props.coursesidtype==="node"?0:attachmentId,
is_public:is_public,
publish_time:Radiovalue===1?datatime===undefined? undefined:datatime:undefined,
description:description,
delay_publish:Radiovalue,
}).then((result)=>{
if(result.data.status===0){
this.ModalCancelModalCancel();
this.props.updataleftNavfun();
this.props.showNotification("上传资源成功");
this.props.setupdate(1,false)
}else{
this.props.showNotification(result.data.message);
}
})
}
}
settextarea=(e)=>{
this.setState({
description:e.target.value
})
}
onChangepublic=(e)=>{
this.setState({
is_public:e.target.checked
})
}
onChangeTimepublish= (date, dateString,key,type) => {
if(type===1){
this.setState({
datatime:handleDateString(dateString),
})
}else if(type===2){
let {course_group_publish_times}=this.state;
let newgroup_publish=course_group_publish_times;
for(var i=0; i<newgroup_publish.length; i++){
if(i===parseInt(key)){
newgroup_publish[i].publish_time=handleDateString(dateString);
}
}
this.setState({
course_group_publish_times:newgroup_publish,
})
}
}
RadioonChange=(e)=>{
if(e.target.value===0){
this.setState({
datatime:undefined
})
}
this.setState({
Radiovalue: e.target.value,
});
}
handleChanges=(e)=>{
let les=e.target.value.length;
if(e.target.value.length>=61){
}else{
this.setState({
resourcesname:e.target.value,
addonAfteronelens3:les,
resourcesnamebool:false,
})
}
}
handleChangess=(e)=>{
this.setState({
resourceurl:e.target.value,
resourceurlbool:false,
})
}
render(){
let { newfileListtype,descriptiontype,
is_public,
datatime,
resourcesname,
resourceurl,
addonAfteronelens3,
resourceurlbool,
resourcesnamebool,
}=this.state;
const uploadProps = {
width: 600,
// showUploadList:false,
action: `${getUploadActionUrl()}`,
onChange: this.handleChange,
onRemove: this.onAttachmentRemove,
beforeUpload: (file) => {
// console.log('beforeUpload', file.name);
const isLt150M = file.size / 1024 / 1024 < 150;
if (!isLt150M) {
this.props.showNotification('文件大小必须小于150MB!');
}
return isLt150M;
},
};
const radioStyle = {
display: 'block',
height: '30px',
lineHeight: '30px',
};
return(
<div>
{/*提示*/}
<Modals
modalsType={this.state.Modalstype}
modalsTopval={this.state.Modalstopval}
modalCancel={this.state.ModalCancel}
modalSave={this.state.ModalSave}
loadtype= {this.state.loadtype}
/>
<Modal
keyboard={false}
className={"HomeworkModal"}
title={this.props.modalname}
visible={this.props.visible}
closable={false}
footer={null}
destroyOnClose={true}
>
<div className="task-popup-content">
<p className="task-popup-text-center font-16">
<span className={"color-blue underline"}> </span>
</p>
<style>{`
.uploadBtn.ant-btn {
border: none;
color: #4CACFF;
box-shadow: none;
background: transparent;
border:1px solid #4CACFF;
padding-left: 6px;
margin-right: 5px;
}
.ant-upload-list-item:hover .ant-upload-list-item-info{
padding: 0 12px 0 0px;
background-color:#fff;
}
.upload_1 .ant-upload-list {
width: 350px;
}
.ant-upload-select{
float: left;
}
.winth540{
width: 540px;
height: 34px;
}
.winth540 .ant-input-group-wrapper{
width: 86% !important;
}
.flexoo .ant-input{
width: 86% !important;
}
`}</style>
<p className={"winth540"} style={{
width: '100%',
display: "flex",
flexDirection:"row",
}}>
<span className="color-ooo fl mt6">资源名称</span>
<Input className="" placeholder="请输入资源名称最大限制60字符" value={resourcesname} onChange={this.handleChanges} addonAfter={String(addonAfteronelens3)+"/60"} maxLength={60} />
</p>
{
resourcesnamebool?
<p className=" color-red" style={{
marginLeft: "13%"
}}>请输入资源名称</p>
:
""
}
<p className={resourcesnamebool===true?"winth540 flexoo mt10":"winth540 flexoo mt16"} style={{
width: '100%',
display: "flex",
flexDirection:"row",
}}>
<span className="color-ooo fl mt6">链接地址</span>
<Input className="" placeholder="请输入外链url" value={resourceurl} onChange={this.handleChangess} />
</p>
{
resourceurlbool?
<p className=" color-red" style={{
marginLeft: "13%"
}}>请输入外链url</p>
:
""
}
<p className={this.state.fileListtype===true?"mt25":""}>
<style>{`
.ant-checkbox-wrapper{
margin-left:0px !important;
margin-top:10px;
}
`}</style>
{this.props.course_is_public===true?<div>
<span className={"color-ooo"}>公开</span><Checkbox checked={is_public} onChange={this.onChangepublic}>
<span className={"font-14 color-ooo"}>选中所有用户可见否则课堂成员可见</span>
</Checkbox>
</div>:""}
<style>{`
.Selectleft20{
margin-left: 20px !important;
width: 176px;
height: 40px;
}
#startimes .ant-calendar-picker-icon{
margin-top:-11px;
}
.resourcebox{
max-height:150px;
overflow: auto;
}
`}</style>
</p>
<div className={this.props.course_is_public===true?"mt10":""}>
<span className={"color-ooo fl mt6"}>发布设置</span>
<Radio.Group onChange={this.RadioonChange} value={this.state.Radiovalue} style={{'width': '460px'}}>
<Radio style={radioStyle} value={0}>
立即发布
</Radio>
<Tooltip placement="bottom" title={this.props.isStudent()===true?"不支持学生延迟发布":""}>
<Radio style={radioStyle} value={1} className={"fl"} disabled={this.props.isStudent()}>
<span className={"mr5"}>延期发布</span>
<DatePicker
dropdownClassName="hideDisable"
showTime={{ format: 'HH:mm' }}
locale={locale}
format={dateFormat}
placeholder="请选择发布时间"
id={"startime"}
showToday={false}
width={"210px"}
value={this.state.Radiovalue===1?datatime===undefined||datatime===""?undefined:moment(datatime, dateFormat):undefined}
onChange={(e,index)=>this.onChangeTimepublish(e,index,undefined,1)}
disabledTime={disabledDateTime}
disabledDate={disabledDate}
disabled={this.state.Radiovalue===1?false:true}
/>
</Radio>
</Tooltip>
<span className={"fl mt5 color-grey-c"}>(按照设置的时间定时发布)</span>
</Radio.Group>
</div>
<div className="mt10" style={{
width: '100%',
display: "flex",
flexDirection:"row",
}}>
<span className="color-ooo fl mt6">资源描述</span>
{/*{course_group_publish_timestype===true?<p className={"color-red mt10"}>请填写完整</p>:""}*/}
<textarea placeholder="请在此输入资源描述最大限制100个字符" className={"mt10"} value={this.state.description} onInput={this.settextarea} style={{
width: '86%',
height:'120px',
border:'1px solid rgba(234,234,234,1)',
padding: '10px'
}}></textarea>
</div>
{descriptiontype===true?<p className={"color-red"}>请输入资源描述最大限制100个字符</p>:""}
{this.state.Radiovaluetype===true?<p className={"color-red"}>发布时间不能为空</p>:""}
<div className="clearfix mt30 edu-txt-center mb10">
<a className="task-btn color-white mr70" onClick={this.props.Cancel}>{this.props.Cancelname}</a>
<a className="task-btn task-btn-orange" onClick={()=>this.Saves()}>{this.props.Savesname}</a>
</div>
</div>
</Modal>
</div>
)
}
}
export default sendResources;

@ -50,7 +50,6 @@ const RightPane = (props) => {
let timer = null; // 定时器
// 代码块内容变化时
const handleCodeChange = (value) => {
// console.log('编辑器代码 ======》》》》》》》》》++++++++++', value);
saveUserInputCode(value);
// setEditorCode(value);
if (!timer) {

@ -742,9 +742,6 @@ class Question extends Component {
}
// 撤销
getitem_basketss=(id)=>{
this.setState({
})
if(Undoclickable===true){
Undoclickable=false;
//选用题型可以上传单个 或者多个题型

@ -232,39 +232,6 @@ class Contentpart extends Component {
}
const content = (
<div className="questiontypes" style={{
width:'93px',
height:'200px',
}}>
<p className="questiontype " onClick={()=>this.props.setitem_types(null)}>全部</p>
<p className="questiontypeheng" ></p>
<p className="questiontype " onClick={()=>this.props.setitem_types("SINGLE")}>单选题</p>
<p className="questiontypeheng" ></p>
<p className="questiontype " onClick={()=>this.props.setitem_types("MULTIPLE")}>多选题</p>
<p className="questiontypeheng"></p>
<p className="questiontype " onClick={()=>this.props.setitem_types("JUDGMENT")}>判断题</p>
<p className="questiontypeheng"></p>
<p className="questiontype " onClick={()=>this.props.setitem_types("PROGRAM")}>编程题</p>
<p className="questiontypeheng"></p>
</div>
);
const contents = (
<div className="questiontypes" style={{
width:'93px',
height:'120px',
}}>
<p className="questiontype " onClick={()=>this.props.setoj_status(null)}>全部</p>
<p className="questiontypeheng"></p>
<p className="questiontype " onClick={()=>this.props.setoj_status(0)}>未发布</p>
<p className="questiontypeheng"></p>
<p className="questiontype " onClick={()=>this.props.setoj_status(1)}>已发布</p>
<p className="questiontypeheng"></p>
</div>
);
// console.log("Contentpart.js");
// console.log(this.props.current_user.professional_certification);
@ -381,22 +348,6 @@ class Contentpart extends Component {
:""
}
{item_type==="PROGRAM"?
defaultActiveKey===0||defaultActiveKey==="0"?
<Popover getPopupContainer={trigger => trigger.parentNode} placement="bottom" trigger="hover" content={contents} onVisibleChange={()=>this.props.handleVisibleChange(true)}>
<div className=" sortinxdirection mr30">
<div className="subjecttit">
全部
</div>
<i className="iconfont icon-sanjiaoxing-down font-12 lg ml7 icondowncolor"></i>
</div>
</Popover>
:
"":""
}
{
this.props.Isitapopup&&this.props.Isitapopup==="true"?
<Search
@ -455,6 +406,7 @@ class Contentpart extends Component {
: this.props.Contentdata.items.map((object, index) => {
return (
<Listjihe {...this.state} {...this.props}
defaultActiveKeys={defaultActiveKeys}
Datacountbool={this.props.Datacountbool}
Datacount={this.props.Datacount}
Isitapopup={this.props.Isitapopup}

@ -346,9 +346,17 @@ class Listjihe extends Component {
</div>
{
items.item_type==="PROGRAM"?
<a onClick={()=>this.seturls(`/problems/${items.program_attr.identifier}/edit`)} className="ml10 flex1 mt2">
<div style={{wordBreak: "break-word",fontWeight:"bold"}} dangerouslySetInnerHTML={{__html: markdownToHTML(items&&items.name).replace(/▁/g, "▁▁▁")}}></div>
</a>
(
this.props.defaultActiveKeys&&this.props.defaultActiveKeys==="0"?
<a onClick={()=>this.seturls(`/problems/${items.program_attr.identifier}/edit`)} className="ml10 flex1 mt2 xiaoshou">
<div style={{wordBreak: "break-word",fontWeight:"bold"}} dangerouslySetInnerHTML={{__html: markdownToHTML(items&&items.name).replace(/▁/g, "▁▁▁")}}></div>
</a>
:
<p className="ml10 flex1 mt2 xiaoshout">
<div style={{wordBreak: "break-word",fontWeight:"bold"}} dangerouslySetInnerHTML={{__html: markdownToHTML(items&&items.name).replace(/▁/g, "▁▁▁")}}></div>
</p>
)
:
<div className="ml10 flex1 markdown-body mt2" style={{wordBreak: "break-word",fontWeight:"bold"}}>

@ -28,11 +28,21 @@ class QuestionModal extends Component {
width="442px"
>
<div className="educouddiv">
<div className={"tabeltext-alignleft mt10"}><p className="titiles">{this.props.titilesm}</p></div>
<div className={"tabeltext-alignleft mt10"}><p className="titiles">{this.props.titiless}</p></div>
{this.props.titilesm?
<div className={"tabeltext-alignleft mt10"}><p className="titiles">{this.props.titilesm}</p></div>
:
""
}
{
this.props.titiless?
<div className={"tabeltext-alignleft mt10"}><p className="titiles">{this.props.titiless}</p></div>
:
""
}
<div className="clearfix mt30 edu-txt-center">
<a className="task-btn mr30 w80" onClick={()=>this.props.modalCancel()}>取消</a>
<a className="task-btn task-btn-orange w80" onClick={()=>this.props.setDownload()}>确定</a>
<a className="task-btn task-btn-orange w80" onClick={()=>this.props.setDownload()}>{this.props.boolok?this.props.boolok:"确定"}</a>
</div>
</div>
</Modal>

@ -19,6 +19,7 @@ import '../tpm/newshixuns/css/Newshixuns.css';
import Bottomsubmit from "../../modules/modals/Bottomsubmit";
import Seeoagertit from "./component/Seeoagertit";
import Paperlibraryseeid_item from './component/Paperlibraryseeid_item';
import QuestionModal from "../question/component/QuestionModal";
//人工组卷预览
class Paperlibraryseeid extends Component {
constructor(props) {
@ -27,8 +28,10 @@ class Paperlibraryseeid extends Component {
this.state = {
paperlibrartdata:[],
defaultActiveKey:"0",
modalsType:false,
titilesm:"",
titiless:"",
boolok:"知道了"
}
@ -91,7 +94,11 @@ class Paperlibraryseeid extends Component {
}
preservation = () => {
//保存试卷
this.setState({
modalsType: true,
titilesm: "功能正在内测中,敬请期待",
titiless: "",
})
@ -109,13 +116,34 @@ class Paperlibraryseeid extends Component {
getcontentMdRef = (Ref) => {
this.contentMdRef = Ref;
}
modalCancel =()=>{
this.setState({
modalsType: false,
})
}
setDownload =()=>{
this.setState({
modalsType: false,
})
}
render() {
let {paperlibrartdata,defaultActiveKey} = this.state;
let {paperlibrartdata,defaultActiveKey,titilesm,titiless,boolok,modalsType} = this.state;
const params = this.props && this.props.match && this.props.match.params;
// ////console.log(params);
let urlsysl=`/paperlibrary?defaultActiveKey=${defaultActiveKey}`;
return (
<div>
{
modalsType===true?
<QuestionModal {...this.props}{...this.state} modalsType={modalsType} modalCancel={() => this.modalCancel()}
setDownload={() => this.setDownload()}></QuestionModal>
:""
}
<div id={"Itembankstopid"} className="newMain clearfix intermediatecenter "
>

@ -91,7 +91,7 @@ export default ({ StatusEnquiry, allUpdatashixunlist, Updatasearchlist }) => {
<div className="clearfix mb20 shaiContent">
<span className="shaiTitle fl">方向</span>
<div className="fl pr shaiAllItem">
<a className={searchValue === "a" ? "shaiItem shixun_repertoire active" : "shaiItem shixun_repertoire"} value="a" onClick={onSearchAll}>全部</a>
<li className={searchValue === "a" ? "shaiItem shixun_repertoire active" : "shaiItem shixun_repertoire"} value="a" onClick={onSearchAll}>全部</li>
{
navs.map((item, key) => {
return (

@ -73,7 +73,7 @@ function doCreateUploader (options) {
const _random = '' // Math.random().toString().substring(3, 6)+'-'
axios.post(createUrl, {
title: _random+fileName,
file_name: _random+fileName
file_name: _random+fileName,
}).then((response) => {
// if (response.data.status == )
if(response){

@ -8,10 +8,12 @@ function EditVideoModal (props) {
const modalEl = useRef(null);
const theme = useContext(ThemeContext);
const { history, videoId, cover_url, title, created_at, isReview, onEditVideo, visible, setVisible,
form, editSuccess } = props;
form, editSuccess , link } = props;
const getFieldDecorator = form.getFieldDecorator
let username = props.match.params.username
const _title = form.getFieldsValue().title;
const _link = form.getFieldsValue().link;
if(props.CourseUser){
username = props.CourseUser;
@ -27,9 +29,14 @@ function EditVideoModal (props) {
form.validateFieldsAndScroll((err, values) => {
if (!err) {
const url = `/users/${username}/videos/${videoId}.json`
axios.put(url, {
title: _title
const url = link?`/course_videos/${videoId}.json`:`/users/${username}/videos/${videoId}.json`;
axios.put(url, link ? {
name:_title,
link:_link
}:{
title: _title,
link:_link
}).then((response) => {
if (response.data) {
onCancel()
@ -54,7 +61,7 @@ function EditVideoModal (props) {
}, [visible])
useEffect(() => {
visible && form.setFieldsValue({
title,
title,link
})
}, [visible])
return (
@ -71,10 +78,9 @@ function EditVideoModal (props) {
{
`
.exercicenewinputysl .ant-input{
border-right: none !important;
height: 40px !important;
}
border-right: none !important;
height: 40px !important;
}
`
}
</style>
@ -93,7 +99,24 @@ function EditVideoModal (props) {
<Input placeholder="" className="titleInput exercicenewinputysl" maxLength={MAX_LENGTH}
addonAfter={String(_title ? `${String(_title.length)}/${MAX_LENGTH}` : 0)} />
)}
</Form.Item>
</Form.Item>
{
link ?
<Form.Item
label="视频链接"
className="title formItemInline"
>
{getFieldDecorator('link', {
rules: [{
required: true, message: '请输入视频链接',
}],
})(
<Input placeholder="请输入视频链接" className="titleInput exercicenewinputysl" />
)}
</Form.Item>
:""
}
</ModalWrapper>
)
}

@ -142,4 +142,26 @@
.videoItem:hover{
box-shadow:0px 4px 10px 0px rgba(3,7,45,0.1);
border-radius:12px;
}
.otherLink{
position: absolute;
height:30px;
line-height: 30px;
padding:0px 18px;
background:rgba(249,117,26,1);
border-radius:0px 100px 100px 0px;
display: block;
left: 0;
top:32px;
color: #fff;
z-index:2;
}
.otherLinkPanel{
display: block;
position: absolute;
width: 100%;
top:0px;
left:0px;
height: 220px;
z-index: 1;
}

@ -5,7 +5,8 @@ import axios from 'axios'
import moment from 'moment'
import playIcon from './images/play.png'
import ClipboardJS from 'clipboard'
import defaultImg from './images/default.png';
import './InfosVideo.css';
/**
cover_url: "http://video.educoder.net/f6ba49c3944b43ee98736898e31b7d88/snapshots/12da3f7df07c499b8f0fc6dc410094e9-00005.jpg"
created_at: "2019-08-12 13:48:26"
@ -20,7 +21,7 @@ const clipboardMap = {}
function VideoInReviewItem (props) {
const theme = useContext(ThemeContext);
const { history, file_url, cover_url, title, created_at, published_at, isReview, id
, onEditVideo, onMaskClick, getCopyText, showNotification,vv,play_duration,operation , deleteVideo} = props;
, onEditVideo, onMaskClick, getCopyText, showNotification,vv,play_duration,operation , deleteVideo , moveVideo ,link} = props;
useEffect(()=> {
if (!isReview) {
_clipboard = new ClipboardJS(`.copybtn_item_${id}`);
@ -46,12 +47,16 @@ function VideoInReviewItem (props) {
return (
<div className={`${isReview ? 'videoInReviewItem' : 'nItem'} videoItem`}>
<img className="cover" src={cover_url || "http://video.educoder.net/e7d18970482a46d2a6f0e951b504256c/snapshots/491e113950d74f1dab276097dae287dd-00005.jpg"}
></img>
{!isReview && <div className="mask" onClick={() => onMaskClick(props)}>
<img className="cover" src={cover_url || defaultImg} alt=""></img>
{ link ?
<a href={link} target='_blank' className="otherLinkPanel">
<span className="otherLink">外链</span>
</a>
: ""
}
</div>}
{!isReview &&
{!isReview && <div className="mask" onClick={() => onMaskClick(props)}></div>}
{!isReview && !link &&
<div className="playWrap" onClick={() => onMaskClick(props)}>
<img className="play mp23" src={playIcon}></img>
{play_duration===0?"":<div className={"play_duration"}>累计学习时长{play_duration} h</div>}
@ -68,11 +73,19 @@ function VideoInReviewItem (props) {
<div className="df buttonRow">
{/* 2019-09-01 10:00:22 */}
<span className={"dianjilianicon"}>
{vv===0?"":<Tooltip title="播放次数" placement="bottom">
{!vv || (vv && vv)===0 ? "" : <Tooltip title="播放次数" placement="bottom">
<i className={`icon-dianjiliang iconfont dianjilianicon`}></i>
</Tooltip> } {vv===0?"":vv}
</Tooltip> } {!vv || (vv && vv)===0?"":vv}
</span>
{ isReview != true && <div>
{
moveVideo &&
<Tooltip title="移动到" placement="bottom">
<i className="icon-yidong iconfont font-15" onClick={() => moveVideo(props)}
style={{ marginTop: '1px', display: 'inline-block'}}
></i>
</Tooltip>
}
{
deleteVideo &&
<Tooltip title="删除" placement="bottom">
@ -90,9 +103,13 @@ function VideoInReviewItem (props) {
></i>
</Tooltip>
}
<Tooltip title="复制视频地址" placement="bottom">
<i className={`icon-fuzhi iconfont copybtn_item_${id}`} data-clipboard-text={getCopyText(file_url, cover_url)}></i>
</Tooltip>
{
!link ?
<Tooltip title="复制视频地址" placement="bottom">
<i className={`icon-fuzhi iconfont copybtn_item_${id}`} data-clipboard-text={getCopyText(file_url, cover_url)}></i>
</Tooltip>:""
}
</div> }
</div>
</div>

@ -226,7 +226,7 @@ function VideoUploadList (props) {
}
function onPublish() {
// 下列这些参数只有是课堂里面上传视频才会有
const { CourseId , CourseUser ,flag , successFunc } = props;
const { CourseId , CourseUser ,flag , successFunc , videoId } = props;
if (state.videos.length == 0) {
showNotification('请先上传视频')
return;
@ -238,7 +238,8 @@ function VideoUploadList (props) {
video_id: item.videoId,
// todo
title: item.title,
course_id:CourseId
course_id:CourseId,
category_id:videoId
}
})
}).then((response) => {
@ -268,7 +269,6 @@ function VideoUploadList (props) {
:
<Link to={`/users/${username}/videos/protocol`} style={{color: theme.foreground_select}}>上传内容协议</Link>
const protocolLine = <div>上传视频即表示您已同意{urls}不得上传未经他人授权的作品</div>
return (
<div className={flag?"edu-back-white pb100 videoUploadList":"educontent videoUploadList"} style={{ marginBottom: `${flag?"0px":"200px"}` }}>
<Prompt

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

@ -133,33 +133,29 @@ export const saveUserCodeForInterval = (identifier, code) => {
* @param {*} inputValue 输入值: 自定义 | 系统返回的
* @param {*} type 测评类型 debug | submit
*/
//原来的方法未能区分从编辑入口进来的情况,这时代码也是更新了的。
//原来的方法未能区分从编辑入口进来的情况,这时代码也是更新了的。以及ctrl+z undo未能触发chnage事件 monaco-editor的bug。 这里去除isUpdateCode
export const updateCode = (identifier, inputValue, type) => {
return (dispatch, getState) => {
const { editor_code, isUpdateCode } = getState().ojForUserReducer;
if (isUpdateCode) {
fetchUpdateCode(identifier, {
code: Base64.encode(editor_code)
}).then(res => {
if (res) {
if (res.data.status === 401) {
dispatch({ // 改变 loading 值
type: types.LOADING_STATUS,
payload: false
});
return;
};
dispatch({
type: types.IS_UPDATE_CODE,
flag: false
const { editor_code } = getState().ojForUserReducer;
fetchUpdateCode(identifier, {
code: Base64.encode(editor_code)
}).then(res => {
if (res) {
if (res.data.status === 401) {
dispatch({ // 改变 loading 值
type: types.LOADING_STATUS,
payload: false
});
dispatch(debuggerCode(identifier, inputValue, type));
}
});
} else {
// 没有更新时,直接调用调试接口
dispatch(debuggerCode(identifier, inputValue, type));
}
return;
};
dispatch({
type: types.IS_UPDATE_CODE,
flag: false
});
dispatch(debuggerCode(identifier, inputValue, type));
}
});
}
}
@ -370,14 +366,6 @@ export const saveUserInputCode = (code) => {
}
}
// 监听是否更新代码块内容
// export const isUpdateCodeCtx = (flag) => {
// return {
// type: types.IS_UPDATE_CODE,
// payload: flag
// };
// }
// 改变学员测评 tab 值
export const changeUserCodeTab = (key) => {
return {
@ -392,7 +380,7 @@ export const changeUserCodeTab = (key) => {
*/
export const submitUserCode = (identifier, inputValue, type) => {
return (dispatch, getState) => {
const { editor_code, isUpdateCode, hack } = getState().ojForUserReducer;
const { editor_code, hack } = getState().ojForUserReducer;
function userCodeSubmit() {
fetchUserCodeSubmit(identifier).then(res => {
if (res.status === 200) {
@ -413,32 +401,29 @@ export const submitUserCode = (identifier, inputValue, type) => {
});
});
}
if (isUpdateCode) {
fetchUpdateCode(identifier, {
code: Base64.encode(editor_code)
}).then(res => {
// 是否更新了代码, 目的是当代码没有更新时不调用更新代码接口,目录没有实现
if (res.data.status === 401) {
dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
});
return;
};
dispatch({
type: types.IS_UPDATE_CODE,
flag: false
});
userCodeSubmit();
}).catch(() => {
fetchUpdateCode(identifier, {
code: Base64.encode(editor_code)
}).then(res => {
// 是否更新了代码, 目的是当代码没有更新时不调用更新代码接口,目录没有实现
if (res.data.status === 401) {
dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
})
});
return;
};
dispatch({
type: types.IS_UPDATE_CODE,
flag: false
});
} else {
userCodeSubmit();
}
}).catch(() => {
dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
})
});
}
}

@ -17,7 +17,6 @@ const initialState = {
commitTestRecordDetail: {}, // 调试代码执行结果
commitRecordDetail: {}, // 提交成功后记录提交的详情
commitRecord: [], // 提交记录
userCode: '', // 保存当前用户输入的代码
isUpdateCode: false, // 是否更新了代码内容
userCodeTab: 'task', // 学员测评tab位置: task | record | comment
userTestInput: '', // 用户自定义输入值
@ -59,7 +58,7 @@ const ojForUserReducer = (state = initialState, action) => {
hack: Object.assign({}, hack),
test_case: Object.assign({}, test_case),
comment_identifier: hack.identifier,
userCode: tempCode
editor_code: tempCode
}
case types.COMMIT_RECORD_DETAIL:
let result = action.payload.data;
@ -127,7 +126,8 @@ const ojForUserReducer = (state = initialState, action) => {
}
return {
...state,
recordDetail: tempDetail
recordDetail: tempDetail,
editor_code: tempDetail['code']
}
case types.RESTORE_INITIAL_CODE:
const curHack = state.hack;

Loading…
Cancel
Save