chromesetting
cxt 5 years ago
commit 4009ed9d2c

@ -5,7 +5,7 @@ class Admins::SchoolsController < Admins::BaseController
schools = Admins::SchoolQuery.call(params)
@schools = paginate schools.includes(:user_extensions)
@schools = paginate schools
school_ids = @schools.map(&:id)
@department_count = Department.where(school_id: school_ids).group(:school_id).count

@ -1,7 +1,7 @@
class CollegesController < ApplicationController
include PaginateHelper
layout 'college'
# layout 'college'
before_action :require_login
before_action :check_college_present!
@ -21,6 +21,7 @@ class CollegesController < ApplicationController
# 实训总数
@shixuns_count = Shixun.visible.joins('left join user_extensions on user_extensions.user_id = shixuns.user_id')
.where(user_extensions: { school_id: current_school.id }).count
render json: { teachers_count: @teachers_count, students_count: @students_count, courses_count: @courses_count, shixuns_count: @shixuns_count, school: current_school.name }
end
def shixun_time
@ -43,6 +44,8 @@ class CollegesController < ApplicationController
(SELECT count(c.id) FROM courses c, course_members m WHERE c.id != 1309 and m.course_id = c.id AND m.user_id=users.id AND m.role in (1,2,3) and c.school_id = #{current_school.id} AND c.is_delete = 0) as course_count
FROM `users`, user_extensions ue where ue.school_id=#{current_school.id} and users.id=ue.user_id and ue.identity=0 ORDER BY publish_shixun_count desc, course_count desc, id desc LIMIT 10")
# ).order("publish_shixun_count desc, experience desc").limit(10)
# @teacher_count = UserExtension.where(school_id: current_school.id)
# .select('SUM(IF(identity=0, 1, 0)) AS teachers_count').first.teachers_count
@teachers =
@teachers.map do |teacher|
course_ids = Course.find_by_sql("SELECT c.id FROM courses c, course_members m WHERE c.id != 1309 and m.course_id = c.id AND m.role in (1,2,3) AND m.user_id=#{teacher.id} AND c.is_delete = 0 and c.school_id = #{current_school.id}")
@ -94,11 +97,11 @@ class CollegesController < ApplicationController
def course_statistics
courses = Course.where(school_id: current_school.id, is_delete: 0).where.not(id: 1309)
@course_count = courses.size
courses = courses.left_joins(practice_homeworks: { student_works: { myshixun: :games } })
.select('courses.id, courses.name, courses.is_end, sum(games.evaluate_count) evaluating_count')
.select('courses.id, courses.name, courses.is_end, IFNULL(sum(games.evaluate_count), 0) evaluating_count')
.group('courses.id').order('is_end asc, evaluating_count desc')
params[:per_page] = 8
@courses = paginate courses
course_ids = @courses.map(&:id)
@ -114,6 +117,7 @@ class CollegesController < ApplicationController
# 学生实训
def student_shixun
# @student_count = User.joins(:user_extension).where(user_extensions: { school_id: current_school.id, identity: 1 }).count
@students = User.joins(:user_extension).where(user_extensions: { school_id: current_school.id, identity: 1 }).includes(:user_extension).order('experience desc').limit(10)
student_ids = @students.map(&:id)
@ -154,7 +158,7 @@ class CollegesController < ApplicationController
return true if current_user.admin_or_business? # 超级管理员|运营
return true if current_college.is_a?(Department) && current_college.member?(current_user) # 部门管理员
return true if current_user.is_teacher? && current_user.school_id == current_school.id # 学校老师
return true if current_school.customers.exists? && current_user.partner&.partner_customers&.exists?(customer_id: current_school.customer_id)
# return true if current_school.customers.exists? && current_user.partner&.partner_customers&.exists?(customer_id: current_school.customer_id)
false
end

@ -9,9 +9,11 @@ class HacksController < ApplicationController
def start
# 未发布的编程题,只能作者、或管理员访问
start_hack_auth
user_hack = @hack.hack_user_lastest_codes.mine(current_user.id)
user_hack = @hack.hack_user_lastest_codes.where(user_id: current_user.id).first
logger.info("#user_hack: #{user_hack}")
identifier =
if user_hack.present?
logger.info("#####user_hack_id:#{user_hack.id}")
user_hack.identifier
else
user_identifier = generate_identifier HackUserLastestCode, 12

@ -167,7 +167,11 @@ class HomeworkCommonsController < ApplicationController
if params[:work_status].present?
params_work_status = params[:work_status]
work_status = params_work_status.map{|status| status.to_i}
@student_works = @student_works.where(compelete_status: work_status)
if @homework.homework_type == "practice"
@student_works = @student_works.where(compelete_status: work_status)
else
@student_works = @student_works.where(work_status: work_status)
end
end
# 分班情况

@ -0,0 +1,14 @@
class LibrariesController < ApplicationController
include PaginateHelper
def index
default_sort('updated_at', 'desc')
@items = ItemBankQuery.call(params)
@items = paginate courses.includes(:school, :students, :attachments, :homework_commons, teacher: :user_extension)
end
def create
end
end

@ -1,9 +1,35 @@
require 'base64'
class JupytersController < ApplicationController
include JupyterService
before_action :shixun, only: [:open, :open1, :test, :save]
def import_with_tpm
shixun = Shixun.find_by(identifier: params[:identifier])
upload_file = params["file"] || params["#{params[:file_param_name]}"] # 这里的file_param_name是为了方便其他插件名称
uid_logger("#########################file_params####{params["#{params[:file]}"]}")
raise "未上传文件" unless upload_file
content = Base64.encode64(upload_file.tempfile.read)
# upload to server
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/update"
tpiID = "tpm#{shixun.id}"
params = {tpiID: tpiID, content: content}
logger.info "test_juypter: uri->#{uri}, params->#{params}"
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级100")
end
render json: {status: 0}
end
def save_with_tpi
myshixun = Myshixun.find_by(identifier: params[:identifier])
jupyter_save_with_game(myshixun, params[:jupyter_port])
@ -42,5 +68,16 @@ class JupytersController < ApplicationController
render json: {status: 0, url: info[:url], port: info[:port]}
end
def active_with_tpm
shixun = Shixun.find_by(identifier: params[:identifier])
jupyter_active_tpm(shixun)
render json: {status: 0}
end
def active_with_tpi
myshixun = Myshixun.find_by(identifier: params[:identifier])
jupyter_active_tpm(myshixun)
render json: {status: 0}
end
end
end

@ -247,7 +247,7 @@ class MyshixunsController < ApplicationController
def update_file
begin
@hide_code = Shixun.where(id: @myshixun.shixun_id).pluck(:hide_code).first
tip_exception("技术平台为空!") if @myshixun.mirror_name.blank?
tip_exception("实验环境不能为空,请查看实训模板的环境配置项是否正确!") if (@myshixun.mirror_name.blank? || @myshixun.mirror_name.first.to_s == "-1")
path = params[:path].strip unless params[:path].blank?
game_id = params[:game_id]
game = Game.find(game_id)

@ -165,9 +165,10 @@ class ShixunsController < ApplicationController
def show_right
owner = @shixun.owner
#@fans_count = owner.fan_count
#@followed_count = owner.follow_count
@user_own_shixuns = owner.shixuns.published.count
@user_tags = @shixun.user_tags_name(current_user)
@shixun_tags = @shixun.challenge_tags_name
@myshixun = @shixun.myshixuns.find_by(user_id: current_user.id)
end
# 排行榜
@ -345,7 +346,11 @@ class ShixunsController < ApplicationController
#合作者
def collaborators
@user = current_user
@members = @shixun.shixun_members.includes(:user)
## 分页参数
page = params[:page] || 1
limit = params[:limit] || 10
@member_count = @shixun.shixun_members.count
@members = @shixun.shixun_members.includes(:user).page(page).per(limit)
end
def fork_list
@ -758,7 +763,7 @@ class ShixunsController < ApplicationController
else
commit = GitService.commits(repo_path: @repo_path).try(:first)
uid_logger("First comit########{commit}")
tip_exception("开启挑战前请先在Jupyter中填写内容") if commit.blank?
tip_exception("开启挑战前请先在Jupyter中填写内容并保存") if commit.blank?
commit_id = commit["id"]
cloud_bridge = edu_setting('cloud_bridge')
myshixun_identifier = generate_identifier Myshixun, 10

@ -8,7 +8,7 @@ class HackUserLastestCode < ApplicationRecord
belongs_to :user
has_many :hack_user_codes, dependent: :destroy
has_one :hack_user_debug
scope :mine, ->(author_id){ find_by(user_id: author_id) }
scope :mine, ->(author_id){ where(user_id: author_id).first }
scope :mine_hack, ->(author_id){ where(user_id: author_id) }
scope :passed, -> {where(status: 1)}

@ -0,0 +1,3 @@
class ItemAnalysis < ApplicationRecord
belongs_to :item_bank
end

@ -0,0 +1,11 @@
class ItemBank < ApplicationRecord
# difficulty: 1 简单 2 适中 3 困难
# item_type: 0 单选 1 多选 2 判断 3 填空 4 简答 5 实训 6 编程
enum item_type: { SINGLE: 0, MULTIPLE: 1, JUDGMENT: 2, COMPLETION: 3, SUBJECTIVE: 4, PRACTICAL: 5, PROGRAM: 6 }
belongs_to :user
has_one :item_analysis, dependent: :destroy
has_many :item_choices, dependent: :destroy
has_many :item_baskets, dependent: :destroy
end

@ -0,0 +1,4 @@
class ItemBasket < ApplicationRecord
belongs_to :item_bank
belongs_to :user
end

@ -0,0 +1,3 @@
class ItemChoice < ApplicationRecord
belongs_to :item_bank
end

@ -17,6 +17,7 @@ class Admins::SchoolQuery < ApplicationQuery
if keyword
schools = schools.where('schools.name LIKE ?', "%#{keyword}%")
end
schools = schools.left_joins(:user_extensions).select('schools.*, IFNULL(count(user_extensions.user_id),0) users_count').group('schools.id')
custom_sort schools, params[:sort_by], params[:sort_direction]
end
end

@ -191,5 +191,26 @@ module JupyterService
edu_setting('jupyter_service').gsub("PORT", jupyter_port)
end
def _jupyter_active(tpiID)
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/active"
params = {:tpiID => tpiID}
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级120")
end
end
# tpm 延时
def jupyter_active_tpm(shixun)
tpiID = "tpm#{shixun.id}"
_jupyter_active(tpiID)
end
# tpi 延时
def jupyter_active_tpi(myshixun)
tpiID = myshixun.id
_jupyter_active(tpiID)
end
end

@ -33,7 +33,7 @@
<td><%= school.province %></td>
<td><%= school.city %></td>
<td class="text-left"><%= school.address %></td>
<td><%= school.user_extensions.count %></td>
<td><%= school.users_count %></td>
<td><%= @department_count.fetch(school.id, 0) %></td>
<td><%= school.created_at&.strftime('%Y-%m-%d %H:%M') %></td>
<td>

@ -0,0 +1,11 @@
json.courses @courses do |course|
json.(course, :id, :name, :is_end, :evaluating_count)
json.teachers course.teacher_users.map(&:real_name).join('、')
json.student_count @student_count.fetch(course.id, 0)
json.shixun_work_count @shixun_work_count.fetch(course.id, 0)
json.attachment_count @attachment_count.fetch(course.id, 0)
json.message_count @message_count.fetch(course.id, 0)
json.other_work_count @exercise_count.fetch(course.id, 0) + @poll_count.fetch(course.id, 0) + @other_work_count.fetch(course.id, 0)
json.activity_time @active_time[course.id]&.strftime('%Y-%m-%d %H:%M')
end
json.course_count @course_count

@ -0,0 +1,10 @@
json.teachers @students do |student|
json.login student.login
json.name student.real_name
json.student_id student.student_id
json.shixun_count @shixun_count.fetch(student.id, 0)
json.study_shixun_count @study_shixun_count.fetch(student.id, 0)
json.grade student.grade
json.experience student.experience
end
# json.student_count @student_count

@ -0,0 +1,11 @@
json.teachers @teachers do |teacher|
json.login teacher['login']
json.name teacher['real_name']
json.course_count teacher['course_count']
json.shixun_work_count teacher['shixun_work_count']
json.un_shixun_work_count teacher['un_shixun_work_count']
json.student_count teacher['student_count']
json.complete_rate teacher['complete_rate']
json.publish_shixun_count teacher['publish_shixun_count']
end
# json.teacher_count @teacher_count

@ -13,11 +13,17 @@ json.recommands do
json.partial! 'shap_shixun', locals: { shixuns: shixun.relation_path.size > 0 ? recommend_shixun(shixun) : [] }
end
if shixun.status > 1
json.complete_count @myshixun&.passed_count
json.challenge_count @shixun.challenges_count
end
# 技能标签
user_tags = shixun.user_tags_name(User.current)
json.tags do
json.array! shixun.challenge_tags_name do |tag|
json.array! @shixun_tags do |tag|
json.tag_name tag
json.status user_tags.include?(tag)
json.status @user_tags.include?(tag)
end
end
json.tag_count @shixun_tags.size
json.user_tag_count @user_tags.size

@ -6,7 +6,8 @@
# ]
json.array! @members do |member|
json.member_count @member_count
json.members @members do |member|
json.user do
json.partial! 'users/user', locals: { user: member.user }
json.user_shixuns_count member.user.shixuns.published.count

@ -1,6 +1,6 @@
json.partial! 'shixuns/right', locals: { shixun: @shixun }
json.follow follow?(@shixun.owner, User.current)
json.fans_count @fans_count
json.followed_count @followed_count
# json.follow follow?(@shixun.owner, User.current)
# json.fans_count @fans_count
# json.followed_count @followed_count
json.user_shixuns_count @user_own_shixuns

@ -41,7 +41,9 @@ json.top do
json.old_url @old_domain
# 云上实验室管理权限
json.laboratory_user current_laboratory.laboratory_users.exists?(user_id: @user&.id) || @user&.admin_or_business?
laboratory_user = current_laboratory.laboratory_users.exists?(user_id: @user&.id) || @user&.admin_or_business?
json.laboratory_user laboratory_user
json.laboratory_admin_url laboratory_user ? "/cooperative" : nil
end
json.down do

@ -33,6 +33,10 @@ Rails.application.routes.draw do
get :get_info_with_tpm
get :reset_with_tpi
get :reset_with_tpm
get :active_with_tpm
get :active_with_tpi
post :import_with_tpm
end
end
@ -48,7 +52,9 @@ Rails.application.routes.draw do
end
end
resources :item_banks do
end
resources :hacks, path: :problems, param: :identifier do
@ -63,7 +69,9 @@ Rails.application.routes.draw do
delete :delete_set
end
resources :comments do
post :reply
collection do
post :reply
end
end
end
@ -977,6 +985,19 @@ Rails.application.routes.draw do
post :entry
end
end
resources :colleges, only: [] do
member do
get :statistics
get :course_statistics
get :student_shixun
get :shixun_time
get :shixun_report_count
get :teachers
get :shixun_chart_data
get :student_hot_evaluations
end
end
end
namespace :admins do
@ -1287,19 +1308,6 @@ Rails.application.routes.draw do
end
end
resources :colleges, only: [] do
member do
get :statistics
get :course_statistics
get :student_shixun
get :shixun_time
get :shixun_report_count
get :teachers
get :shixun_chart_data
get :student_hot_evaluations
end
end
resources :partners, only: [] do
member do
get :customers

@ -0,0 +1,16 @@
class CreateItemBanks < ActiveRecord::Migration[5.2]
def change
create_table :item_banks do |t|
t.text :name
t.references :curriculum, index: true
t.references :curriculum_direction, index: true
t.integer :item_type
t.integer :difficulty
t.references :user, index: true
t.boolean :public
t.integer :quotes
t.timestamps
end
end
end

@ -0,0 +1,10 @@
class CreateItemAnalyses < ActiveRecord::Migration[5.2]
def change
create_table :item_analyses do |t|
t.references :item_bank, index: true
t.text :analysis
t.timestamps
end
end
end

@ -0,0 +1,11 @@
class CreateItemChoices < ActiveRecord::Migration[5.2]
def change
create_table :item_choices do |t|
t.references :item_bank, index: true
t.text :choice_text
t.boolean :is_answer
t.timestamps
end
end
end

@ -0,0 +1,10 @@
class CreateItemBaskets < ActiveRecord::Migration[5.2]
def change
create_table :item_baskets do |t|
t.references :item_bank, index: true
t.references :user, index: true
t.timestamps
end
end
end

@ -0,0 +1,10 @@
class ModifyDescriptionForHacks < ActiveRecord::Migration[5.2]
def change
change_column :hacks, :description, :longtext
change_column :hack_codes, :code, :longtext
change_column :hack_user_lastest_codes, :code, :longtext
change_column :hack_user_codes, :code, :longtext
change_column :hack_user_debugs, :code, :longtext
end
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,51 @@
//用于嵌入到jupyter pod中的js
//guange 2019.12.18
var timebool=false;
window.onload=function(){
console.log("开始发送消息了");
timebool=true;
// runEvery10Sec();
}
function runEvery10Sec() {
// 1000 * 10 = 10 秒钟
// console.log("每隔10秒中一次");
require(["base/js/namespace"],function(Jupyter) {
Jupyter.notebook.save_checkpoint();
});
window.parent.postMessage('jupytermessage','*');
// if(timebool===true){
// setTimeout( runEvery10Sec, 1000 * 10 );
// }
}
window.onload=function(){
document.addEventListener('keydown', (e) => {
if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)){
e.preventDefault();
console.log("ctrl+s");
window.parent.postMessage('jupytermessage','*');
}
});
window.addEventListener('message', (e) => {
if(e){
if(e.data){
if(e.data==="stopParent"){
//重置停止
timebool=false;
console.log("父窗口调用停止");
}else if(e.data==="clonsParent"){
console.log("父窗口调用启动");
//取消启动
timebool=true;
// runEvery10Sec();
}
}
}
});
}

@ -61,6 +61,7 @@
"prop-types": "^15.6.1",
"qs": "^6.6.0",
"quill": "^1.3.7",
"quill-delta-to-html": "^0.11.0",
"raf": "3.4.0",
"rc-form": "^2.1.7",
"rc-pagination": "^1.16.2",

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -75,6 +75,13 @@
"unicode": "e6bc",
"unicode_decimal": 59068
},
{
"icon_id": "496400",
"name": "禁止",
"font_class": "jinzhi",
"unicode": "e6d4",
"unicode_decimal": 59092
},
{
"icon_id": "562997",
"name": "vs",
@ -180,6 +187,13 @@
"unicode": "e609",
"unicode_decimal": 58889
},
{
"icon_id": "978358",
"name": "浏览",
"font_class": "liulan",
"unicode": "e6c7",
"unicode_decimal": 59079
},
{
"icon_id": "986702",
"name": "路由",
@ -208,6 +222,13 @@
"unicode": "e66d",
"unicode_decimal": 58989
},
{
"icon_id": "1110411",
"name": "下箭头",
"font_class": "jiantou9",
"unicode": "e700",
"unicode_decimal": 59136
},
{
"icon_id": "1113422",
"name": "三角形-up",
@ -495,6 +516,13 @@
"unicode": "e694",
"unicode_decimal": 59028
},
{
"icon_id": "3315084",
"name": "博客园",
"font_class": "bokeyuan",
"unicode": "e6c6",
"unicode_decimal": 59078
},
{
"icon_id": "3330922",
"name": "base",
@ -579,6 +607,13 @@
"unicode": "e67d",
"unicode_decimal": 59005
},
{
"icon_id": "3911796",
"name": "SDK问题",
"font_class": "wenti",
"unicode": "e7dc",
"unicode_decimal": 59356
},
{
"icon_id": "4019861",
"name": "银行卡",
@ -1377,6 +1412,13 @@
"unicode": "e68c",
"unicode_decimal": 59020
},
{
"icon_id": "7501072",
"name": "评论",
"font_class": "pinglun",
"unicode": "e6c8",
"unicode_decimal": 59080
},
{
"icon_id": "7587940",
"name": "工程",
@ -1398,6 +1440,13 @@
"unicode": "e604",
"unicode_decimal": 58884
},
{
"icon_id": "8349119",
"name": "上箭头-填充",
"font_class": "shangjiantou-tianchong",
"unicode": "e733",
"unicode_decimal": 59187
},
{
"icon_id": "8361866",
"name": "主页",
@ -1447,6 +1496,13 @@
"unicode": "e6a1",
"unicode_decimal": 59041
},
{
"icon_id": "9974429",
"name": "省略号",
"font_class": "shenglvehao",
"unicode": "e708",
"unicode_decimal": 59144
},
{
"icon_id": "9977539",
"name": "排序",
@ -1552,6 +1608,13 @@
"unicode": "e6b7",
"unicode_decimal": 59063
},
{
"icon_id": "10809887",
"name": "向上 箭头",
"font_class": "changyongtubiao-xianxingdaochu-zhuanqu-",
"unicode": "e74c",
"unicode_decimal": 59212
},
{
"icon_id": "11222372",
"name": "healthmode",
@ -1593,6 +1656,370 @@
"font_class": "projectx",
"unicode": "e6c4",
"unicode_decimal": 59076
},
{
"icon_id": "11965901",
"name": "绑定手机号",
"font_class": "bangdingshoujihao",
"unicode": "e6ca",
"unicode_decimal": 59082
},
{
"icon_id": "12028723",
"name": "标签",
"font_class": "biaoqian1",
"unicode": "e6ce",
"unicode_decimal": 59086
},
{
"icon_id": "12028724",
"name": "记录",
"font_class": "jilu",
"unicode": "e6cf",
"unicode_decimal": 59087
},
{
"icon_id": "12028725",
"name": "书",
"font_class": "shu",
"unicode": "e6d0",
"unicode_decimal": 59088
},
{
"icon_id": "12028726",
"name": "推荐",
"font_class": "tuijian",
"unicode": "e6d1",
"unicode_decimal": 59089
},
{
"icon_id": "12028727",
"name": "创建者",
"font_class": "chuangjianzhe",
"unicode": "e6d2",
"unicode_decimal": 59090
},
{
"icon_id": "12029022",
"name": "创建者",
"font_class": "chuangjianzhe1",
"unicode": "e6da",
"unicode_decimal": 59098
},
{
"icon_id": "12029023",
"name": "书",
"font_class": "shu1",
"unicode": "e6dc",
"unicode_decimal": 59100
},
{
"icon_id": "12029024",
"name": "标签",
"font_class": "biaoqian2",
"unicode": "e6dd",
"unicode_decimal": 59101
},
{
"icon_id": "12029025",
"name": "记录",
"font_class": "jilu1",
"unicode": "e6de",
"unicode_decimal": 59102
},
{
"icon_id": "12029026",
"name": "推荐",
"font_class": "tuijian1",
"unicode": "e6df",
"unicode_decimal": 59103
},
{
"icon_id": "12031268",
"name": "警告",
"font_class": "jinggao1",
"unicode": "e6e0",
"unicode_decimal": 59104
},
{
"icon_id": "12031648",
"name": "点赞",
"font_class": "dianzan2",
"unicode": "e6e1",
"unicode_decimal": 59105
},
{
"icon_id": "12031742",
"name": "评论",
"font_class": "pinglun1",
"unicode": "e6e2",
"unicode_decimal": 59106
},
{
"icon_id": "12033031",
"name": "对勾",
"font_class": "duigou",
"unicode": "e6e3",
"unicode_decimal": 59107
},
{
"icon_id": "12039315",
"name": "提示",
"font_class": "tishi2",
"unicode": "e6e4",
"unicode_decimal": 59108
},
{
"icon_id": "12039523",
"name": "编辑_Hover",
"font_class": "bianji_Hover",
"unicode": "e6e5",
"unicode_decimal": 59109
},
{
"icon_id": "12039524",
"name": "上移_Hover",
"font_class": "shangyi_Hover",
"unicode": "e6e6",
"unicode_decimal": 59110
},
{
"icon_id": "12039525",
"name": "删除_默认",
"font_class": "shanchu_moren",
"unicode": "e6e7",
"unicode_decimal": 59111
},
{
"icon_id": "12039526",
"name": "下移_Hover",
"font_class": "xiayi_Hover",
"unicode": "e6e8",
"unicode_decimal": 59112
},
{
"icon_id": "12039527",
"name": "删除_Hover",
"font_class": "shanchu_Hover",
"unicode": "e6e9",
"unicode_decimal": 59113
},
{
"icon_id": "12039528",
"name": "下移_默认",
"font_class": "xiayi_moren",
"unicode": "e6ea",
"unicode_decimal": 59114
},
{
"icon_id": "12039529",
"name": "编辑_默认",
"font_class": "bianji_moren",
"unicode": "e6eb",
"unicode_decimal": 59115
},
{
"icon_id": "12040163",
"name": "恢复初始代码",
"font_class": "huifuchushidaima",
"unicode": "e6ec",
"unicode_decimal": 59116
},
{
"icon_id": "12040164",
"name": "再次载入",
"font_class": "zaicizairu",
"unicode": "e6ed",
"unicode_decimal": 59117
},
{
"icon_id": "12040165",
"name": "开关",
"font_class": "kaiguan",
"unicode": "e6ef",
"unicode_decimal": 59119
},
{
"icon_id": "12040167",
"name": "目录",
"font_class": "mulu",
"unicode": "e6f0",
"unicode_decimal": 59120
},
{
"icon_id": "12040168",
"name": "缩小",
"font_class": "suoxiao1",
"unicode": "e6f2",
"unicode_decimal": 59122
},
{
"icon_id": "12040169",
"name": "扩大",
"font_class": "kuoda",
"unicode": "e6f3",
"unicode_decimal": 59123
},
{
"icon_id": "12040170",
"name": "设置",
"font_class": "shezhi3",
"unicode": "e6f4",
"unicode_decimal": 59124
},
{
"icon_id": "12053135",
"name": "隐藏",
"font_class": "yincang2",
"unicode": "e6f5",
"unicode_decimal": 59125
},
{
"icon_id": "12074711",
"name": "消息",
"font_class": "xiaoxi11",
"unicode": "e6f6",
"unicode_decimal": 59126
},
{
"icon_id": "12098941",
"name": "金币",
"font_class": "bianzu1",
"unicode": "e6f7",
"unicode_decimal": 59127
},
{
"icon_id": "12107631",
"name": "显示密码",
"font_class": "xianshimima",
"unicode": "e6f9",
"unicode_decimal": 59129
},
{
"icon_id": "12107632",
"name": "隐藏密码",
"font_class": "yincangmima",
"unicode": "e6fa",
"unicode_decimal": 59130
},
{
"icon_id": "12107887",
"name": "复制",
"font_class": "fuzhi2",
"unicode": "e6fb",
"unicode_decimal": 59131
},
{
"icon_id": "12108608",
"name": "文件",
"font_class": "xingzhuangjiehe",
"unicode": "e6fc",
"unicode_decimal": 59132
},
{
"icon_id": "12108609",
"name": "文件夹",
"font_class": "xingzhuangjiehebeifen",
"unicode": "e6fd",
"unicode_decimal": 59133
},
{
"icon_id": "12108648",
"name": "上传",
"font_class": "shangchuan",
"unicode": "e6fe",
"unicode_decimal": 59134
},
{
"icon_id": "12126798",
"name": "挑战",
"font_class": "tiaozhan",
"unicode": "e6ff",
"unicode_decimal": 59135
},
{
"icon_id": "12126824",
"name": "完成",
"font_class": "wancheng1",
"unicode": "e6cb",
"unicode_decimal": 59083
},
{
"icon_id": "12300755",
"name": "企业账号",
"font_class": "qiyezhanghao",
"unicode": "e6cc",
"unicode_decimal": 59084
},
{
"icon_id": "12300756",
"name": "个人账号",
"font_class": "gerenzhanghao",
"unicode": "e6cd",
"unicode_decimal": 59085
},
{
"icon_id": "12300795",
"name": "右滑",
"font_class": "youhua",
"unicode": "e702",
"unicode_decimal": 59138
},
{
"icon_id": "12300843",
"name": "解锁",
"font_class": "jiesuo",
"unicode": "e703",
"unicode_decimal": 59139
},
{
"icon_id": "12300844",
"name": "锁",
"font_class": "suo1",
"unicode": "e704",
"unicode_decimal": 59140
},
{
"icon_id": "12301512",
"name": "加载失败",
"font_class": "jiazaishibai1",
"unicode": "e6d6",
"unicode_decimal": 59094
},
{
"icon_id": "12319671",
"name": "搜索",
"font_class": "bianzu11",
"unicode": "e706",
"unicode_decimal": 59142
},
{
"icon_id": "12345165",
"name": "类型",
"font_class": "leixing",
"unicode": "e6d5",
"unicode_decimal": 59093
},
{
"icon_id": "12345541",
"name": "标签尖头",
"font_class": "biaoqianjiantou",
"unicode": "e6d7",
"unicode_decimal": 59095
},
{
"icon_id": "12364938",
"name": "笔记",
"font_class": "biji",
"unicode": "e70a",
"unicode_decimal": 59146
},
{
"icon_id": "12371179",
"name": "置顶",
"font_class": "zhiding",
"unicode": "e6d9",
"unicode_decimal": 59097
}
]
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 362 KiB

@ -0,0 +1,35 @@
/*
* @Author: your name
* @Date: 2019-12-20 11:40:56
* @LastEditTime : 2019-12-20 13:38:49
* @LastEditors : Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: /notebook/Users/yangshuming/Desktop/new__educode/educoder/public/react/public/js/jupyter.js
*/
window.onload=function(){
require(["base/js/namespace"],function(Jupyter) {
Jupyter.notebook.save_checkpoint();
});
}
// //子目标父窗口接收子窗口发送的消息
// let message = {type: 'open', link:'需要发送的消息'};
//子窗口向父窗口发送消息,消息中包含我们想跳转的链接
window.parent.postMessage('jupytermessage','需要发送的消息');
// //目标父窗口接收子窗口发送的消息
// window.addEventListener('message', (e)=>{
// let origin = event.origin || event.originalEvent.origin;
// if (origin !== '需要发送的消息') {
// return;
// }else {
// //更换iframe的src,实现iframe页面跳转
// 执行方法
// }
// },false);

@ -72,6 +72,7 @@ const Otherlogin=Loadable({
loading: Loading,
})
const Otherloginstart=Loadable({
loader: () => import('./modules/login/Otherloginstart'),
loading: Loading,
@ -300,6 +301,11 @@ const Developer = Loadable({
loader: () => import('./modules/developer'),
loading: Loading
})
// 学院统计
const College = Loadable({
loader: () => import('./college/College'),
loading: Loading
})
// 开发者编辑模块
const NewOrEditTask = Loadable({
@ -614,7 +620,10 @@ class App extends Component {
{/*/>*/}
<Route path="/shixuns/new" component={Newshixuns}>
</Route>
<Route path="/colleges/:id/statistics"
render={
(props) => (<College {...this.props} {...props} {...this.state} />)
}/>
{/* jupyter */}
<Route path="/tasks/:identifier/jupyter/"
render={
@ -626,11 +635,26 @@ class App extends Component {
<Route path="/tasks/:stageId" component={IndexWrapperComponent}/>
<Route path="/shixuns/:shixunId" component={TPMIndexComponent}>
</Route>
{/*<Route path="/shixuns/:shixunId" component={TPMIndexComponent}>*/}
{/*</Route>*/}
<Route path="/shixuns/:shixunId"
render={
(props)=>(<TPMIndexComponent {...this.props} {...props} {...this.state}></TPMIndexComponent>)
}
></Route>
{/*列表页 实训项目列表*/}
<Route path="/shixuns" component={TPMShixunsIndexComponent}/>
{/*<Route path="/shixuns" component={TPMShixunsIndexComponent}/>*/}
<Route path="/shixuns"
render={
(props)=>(<TPMShixunsIndexComponent {...this.props} {...props} {...this.state}></TPMShixunsIndexComponent>)
}
></Route>
{/*实训课程(原实训路径)*/}
@ -692,6 +716,7 @@ class App extends Component {
}
}
/>
<Route
path="/problems/:id/edit"
render={
@ -706,6 +731,7 @@ class App extends Component {
render={
(props) => (<StudentStudy {...this.props} {...props} {...this.state} />)
} />
<Route path="/problems"
render={
(props) => (<Developer {...this.props} {...props} {...this.state} />)

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

File diff suppressed because it is too large Load Diff

@ -0,0 +1,84 @@
import React, {Component} from "react";
import {WordsBtn} from 'educoder';
import {Table} from "antd";
import {Link,Switch,Route,Redirect} from 'react-router-dom';
const echarts = require('echarts');
function startechart(data,datanane){
var effChart = echarts.init(document.getElementById('shixun_skill_chart'));
var option = {
tooltip : {
trigger: 'item',
formatter: "{d}% <br/>"
},
legend: {
// orient: 'vertical',
// top: 'middle',
bottom: 50,
left: 'center',
data: datanane
},
series : [
{
type: 'pie',
radius : '65%',
center: ['50%', '35%'],
selectedMode: 'single',
data:data,
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
effChart.setOption(option);
}
class Colleagechart extends Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
startechart(this.props.data,this.props.datanane)
}
componentDidUpdate = (prevProps) => {
if (prevProps.data!= this.props.data) {
startechart(this.props.data,this.props.datanane)
}
}
render() {
let {data}=this.props;
return (
<div>
<div
style={{ width:'100%',height:'600px'}}
id="shixun_skill_chart">
</div>
</div>
)
}
}
export default Colleagechart;

@ -0,0 +1,149 @@
import React, {Component} from "react";
import {WordsBtn} from 'educoder';
import {Table} from "antd";
import {Link,Switch,Route,Redirect} from 'react-router-dom';
const echarts = require('echarts');
function startechart(names, values){
var effChart = echarts.init(document.getElementById('shixun_skill_charts'));
var Color = ['#962e66', '#623363', '#CCCCCC', '#9A9A9A', '#FF8080', '#FF80C2', '#B980FF', '#80B9FF', '#6FE9FF', '#4DE8B4', '#F8EF63', '#FFB967'];
var option = {
backgroundColor: '#fff',
grid: {
left: '3%',
right: '8%',
bottom: '15%',
containLabel: true
},
tooltip: {
show: "true",
trigger: 'item',
formatter: '{c0}',
backgroundColor: 'rgba(0,0,0,0.7)', // 背景
padding: [8, 10], //内边距
extraCssText: 'box-shadow: 0 0 3px rgba(255, 255, 255, 0.4);', //添加阴影
axisPointer: { // 坐标轴指示器,坐标轴触发有效
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
}
},
xAxis: {
type: 'value',
axisTick: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#CCCCCC'
}
},
splitLine: {
show: false,
lineStyle: {
color: '#CCCCCC'
}
},
axisLabel: {
textStyle: {
color: '#656565',
fontWeight: 'normal',
fontSize: '12'
},
formatter: '{value}'
}
},
yAxis: {
type: 'category',
axisLine: {
lineStyle: {
color: '#cccccc'
}
},
splitLine: {
show: false
},
axisTick: {
show: false
},
splitArea: {
show: false
},
axisLabel: {
inside: false,
textStyle: {
color: '#656565',
fontWeight: 'normal',
fontSize: '12'
}
},
data: names
},
series: [{
name: '',
type: 'bar',
itemStyle: {
normal: {
show: true,
color: function(params) {
return Color[params.dataIndex]
},
barBorderRadius: 50,
borderWidth: 0,
borderColor: '#333'
}
},
barGap: '0%',
barCategoryGap: '50%',
data: values
}
]
};
effChart.setOption(option);
}
class Colleagechartzu extends Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
startechart(this.props.data,this.props.datavule)
}
componentDidUpdate = (prevProps) => {
if (prevProps.data!= this.props.data) {
startechart(this.props.data,this.props.datavule)
}
}
render() {
let {data}=this.props;
return (
<div>
<div
style={{ width:'100%',height:'600px'}}
id="shixun_skill_charts">
</div>
</div>
)
}
}
export default Colleagechartzu;

@ -0,0 +1,213 @@
.yslstatistic-header {
width: 100%;
height: 240px;
background-image: url('/images/educoder/statistics.jpg');
background-size: 100% 100%;
}
.yslborder{
border: 1px solid;
}
.yslstatistic-header-title{
flex: 1;
display: flex;
align-items: center;
color: #4CACFF;
font-size: 32px;
}
.yslstatistic-header-content{
width: 100%;
display: flex;
justify-content: space-around;
}
.yslstatistic-header-item{
margin-bottom: 22px;
display: flex;
flex-direction: column;
align-items: center;
color: #fff;
}
.yslstatistic-header-item-label{
color: #989898;
}
.yslstatistic-base-item-label{
width: 217px;
text-align: center;
font-size: 16px;
height: 48px;
line-height: 48px;
color: #686868;
background: #F5F5F5;
border-top: 1px solid #EBEBEB;
}
.yslstatistic-base-item-labels{
width: 217px;
text-align: center;
height: 100px;
line-height: 100px;
background: #ffffff;
border-top: 1px solid #EBEBEB;
border-bottom: 1px solid #EBEBEB;
}
.yslstatistic-base-item-labelsp{
color: #000000;
font-size: 24px;
}
.yslstatistic-base-item-labelsspan{
color: #000000;
margin-left: 5px;
font-size: 16px;
}
.jibenshiyong100{
width: 100%;
}
.yslstatistic-header-item-content{
font-size: 24px;
}
/* 中间居中 */
.intermediatecenter{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 简单居中 */
.intermediatecenterysls{
display: flex;
align-items: center;
}
.spacearound{
display: flex;
justify-content: space-around;
}
.spacebetween{
display: flex;
justify-content: space-between;
}
/* 头顶部居中 */
.topcenter{
display: -webkit-flex;
flex-direction: column;
align-items: center;
}
/* x轴正方向排序 */
/* 一 二 三 四 五 六 七 八 */
.sortinxdirection{
display: flex;
flex-direction:row;
}
/* x轴反方向排序 */
/* 八 七 六 五 四 三 二 一 */
.xaxisreverseorder{
display: flex;
flex-direction:row-reverse;
}
/* 垂直布局 正方向*/
/*
*/
.verticallayout{
display: flex;
flex-direction:column;
}
/* 垂直布局 反方向*/
.reversedirection{
display: flex;
flex-direction:column-reverse;
}
.h4{
font-size: 1.5rem;
font-weight: 500 !important;
}
.ysllinjibenshiyong{
font-weight: 500;
line-height: 1.2;
padding: 2rem 1.25rem;
border-bottom: unset;
background:#fff;
}
.linjibenshiyong{
font-weight: 500;
line-height: 1.2;
padding: 2rem 1.25rem;
border-bottom: unset;
background:#fff;
box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1);
border-radius:2px;
}
.yslslinjibenshiyong{
font-weight: 500;
line-height: 1.2;
border-bottom: unset;
box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1);
border-radius:2px;
}
.yinyin{
background: #fff;
box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1);
border-radius:2px;
}
.edu-back-eeee{
background:#EEEEEE !important;
}
.mt-4{
margin-top: 1.5rem !important;
}
.statistic-label{
padding: 2rem 1.25rem;
font-size: 1.5rem;
font-weight: 400 !important;
}
.mb50{
padding-bottom: 50px !important;
}
.mt40{
margin-top: 40px;
}
.mb80{
margin-bottom: 80px;
}
.task-hide{overflow:hidden; white-space: nowrap; text-overflow:ellipsis;}
a:hover{
color:#0056b3;
}
.color-blue{
color: #4CACFF;
}
.color-huang{
color:#ffc107 !important
}
.maxnamewidth105{
max-width: 105px;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
cursor: default;
}
.maxnamewidth247{
max-width: 247px;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
cursor: default;
}
.maxnamewidth340{
max-width: 340px;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
cursor: default;
}

@ -75,6 +75,10 @@ export function getUploadActionUrltwo(id) {
return `${getUrlmys()}/api/shixuns/${id}/upload_data_sets.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
export function getUploadActionUrlthree() {
return `${getUrlmys()}/api/jupyters/import_with_tpm.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
export function getUploadActionUrlOfAuth(id) {
return `${getUrl()}/api/users/accounts/${id}/auth_attachment.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}

@ -0,0 +1,116 @@
/*
* @Description: 评论表单
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 17:32:55
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 17:51:44
*/
import React, { useState } from 'react';
import { Form, Button, Input } from 'antd';
import QuillForEditor from '../../quillForEditor';
import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html'
const FormItem = Form.Item;
function CommentForm (props) {
const {
commentCtxChagne,
onCancel,
onSubmit,
form
} = props;
const { getFieldDecorator } = form;
const [ctx, setCtx] = useState('');
const options = [
['bold', 'italic', 'underline'],
[{header: [1,2,3,false]}],
['blockquote', 'code-block'],
['link', 'image'],
['formula']
];
// const { form: { getFieldDecorator } } = props;
const [showQuill, setShowQuill] = useState(false);
// 点击输入框
const handleInputClick = () => {
setShowQuill(true);
}
// 取消
const handleCancle = () => {
setShowQuill(false);
onCancel && onCancel();
}
// 编辑器内容变化时
const handleContentChange = (content) => {
setCtx(content);
try {
const _html = new QuillDeltaToHtmlConverter(content.ops, {}).convert();
// props.form.setFieldsValue({'comment': _html.replace(/<\/?[^>]*>/g, '')});
props.form.setFieldsValue({'comment': _html});
} catch (error) {
console.log(error);
}
}
// 发送
const handleSubmit = (e) => {
e.preventDefault();
props.form.validateFields((err, values) => {
if (!err) {
setShowQuill(false);
const content = ctx;
props.form.setFieldsValue({'comment': ''});
setCtx('');
console.log(content);
onSubmit && onSubmit(content);
}
});
}
return (
<Form>
<FormItem>
{
getFieldDecorator('comment', {
rules: [
{ required: true, message: '评论内容不能为空'}
],
})(
<Input
onClick={handleInputClick}
placeholder="说点儿什么~"
style={{
height: showQuill ? '0px' : '40px',
overflow: showQuill ? 'hidden' : 'auto',
opacity: showQuill ? 0 : 1,
transition: 'all .3s'
}}
/>
)
}
<QuillForEditor
imgAttrs={{width: '60px', height: '30px'}}
wrapStyle={{
height: showQuill ? 'auto' : '0px',
opacity: showQuill ? 1 : 0,
overflow: showQuill ? 'none' : 'hidden',
transition: 'all 0.3s'
}}
style={{ height: '150px', overflowY: 'auto' }}
placeholder="说点儿什么~"
options={options}
value={ctx}
onContentChange={handleContentChange}
/>
</FormItem>
<FormItem style={{ textAlign: 'right' }}>
<Button onClick={handleCancle}>取消</Button>
<Button onClick={handleSubmit} type="primary" style={{ marginLeft: '10px'}}>发送</Button>
</FormItem>
</Form>
);
}
export default Form.create()(CommentForm);

@ -0,0 +1,32 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-18 10:49:46
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 11:39:23
*/
import './index.scss';
import React from 'react';
import { Icon } from 'antd';
function CommentIcon ({
type, // 图标类型
count, // 评论数
iconClick,
...props
}) {
// 点击图标
const handleSpanClick = () => {
iconClick && iconClick();
}
return (
<span className={`comment_icon_count ${props.className}`} onClick={ handleSpanClick }>
<Icon className="comment_icon" type={type} />
<span className="comment_count">{ count }</span>
</span>
)
}
export default CommentIcon;

@ -0,0 +1,165 @@
/*
* @Description: 评论单列
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 17:35:17
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 18:02:28
*/
import './index.scss';
import React, { useState } from 'react';
import CommentIcon from './CommentIcon';
import { getImageUrl, CNotificationHOC } from 'educoder'
import { Icon } from 'antd';
import moment from 'moment';
// import QuillForEditor from '../../quillForEditor';
import CommentForm from './CommentForm';
// import {ModalConfirm} from '../ModalConfirm';
function CommentItem ({
options,
confirm
}) {
// 显示评论输入框
const [showQuill, setShowQuill] = useState(false);
// 加载更多评论内容
const [showMore, setShowMore] = useState(false);
// 箭头方向
const [arrow, setArrow] = useState(false);
// 删除评论
const deleteComment = () => {
console.log('删除评论...');
confirm({
title: '提示',
content: (<p>确定要删除该条回复吗?</p>),
onOk () {
console.log('点击了删除');
}
});
// ModalConfirm('提示', (<p>确定要删除该条回复吗?</p>), () => {
// console.log('点击了删除');
// });
}
// 评论头像
const commentAvatar = (url) => (
<img className="item-flex flex-image" src='https://b-ssl.duitang.com/uploads/item/201511/13/20151113110434_kyReJ.jpeg' alt=""/>
);
// 评论信息
const commentInfo = () => (
<p className="item-header">
<span className="item-name">用户名</span>
<span className="item-time">{moment(new Date(), 'YYYYMMDD HHmmss').fromNow()}</span>
<span className="item-close"><Icon type="close" onClick={deleteComment}/></span>
</p>
);
// 评论内容
const commentCtx = (ctx) => (
<p className="item-ctx">
这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容这是评论内容
</p>
);
// 加载更多
const handleOnLoadMore = () => {
if (!arrow) {
// 展开所有
} else {
// 收起
}
setArrow(!arrow);
};
// 评论追加内容
const commentAppend = () => {
return (
<ul className="comment_item_append_list">
<li className="comment_item_area">
{commentAvatar()}
<div className="item-flex item-desc">
{commentInfo()}
{commentCtx()}
</div>
</li>
<li className="comment_item_area">
{commentAvatar()}
<div className="item-flex item-desc">
{commentInfo()}
{commentCtx()}
</div>
</li>
<li className="comment_item_area">
{commentAvatar()}
<div className="item-flex item-desc">
{commentInfo()}
{commentCtx()}
</div>
</li>
<li className="comment_item_loadmore" onClick={handleOnLoadMore}>
<p className="loadmore-txt">展开其余23条评论</p>
<p className="loadmore-icon">
<Icon type={!arrow ? 'down' : 'up'}/>
</p>
</li>
</ul>
);
};
// 点击图标
const handleIconClick = () => {}
// 点击评论icon
const handleClickMessage = () => {
setShowQuill(true);
}
// 点击取消
const handleClickCancel = () => {
setShowQuill(false);
}
// 点击保存
const handleClickSubmit = (content) => {
// 保存并关闭
setShowQuill(false);
console.log('获取保存内容', content);
}
return (
<li className="comment_item_area">
{commentAvatar()}
<div className="item-flex item-desc">
{commentInfo()}
{commentCtx()}
{commentAppend()}
<div className="comment_icon_area">
<CommentIcon className='comment-icon-margin' type="eye" count="100" iconClick={handleIconClick}/>
{/* 回复 */}
<CommentIcon
className='comment-icon-margin'
type="message" count="100"
iconClick={handleClickMessage}
/>
{/* 点赞 */}
<CommentIcon/>
</div>
<div
style={{ display: showQuill ? 'block' : 'none'}}
className="comment_item_quill">
<CommentForm
onCancel={handleClickCancel}
onSubmit={handleClickSubmit}
/>
</div>
</div>
</li>
);
}
export default CNotificationHOC() (CommentItem);

@ -0,0 +1,20 @@
/*
* @Description: 评论列表页
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 17:34:00
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 11:48:09
*/
import './index.scss';
import React from 'react';
import CommentItem from './CommentItem';
function CommentList ({}) {
return (
<ul className="comment_list_wrapper">
<CommentItem />
</ul>
);
}
export default CommentList;

@ -0,0 +1,22 @@
/*
* @Description: 评论组件
* @Author: tangjiang
* @Github:
* @Date: 2019-12-17 17:31:33
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 11:47:39
*/
import React from 'react';
import CommentForm from './CommentForm';
import CommentList from './CommentList';
function Comment (props) {
return (
<React.Fragment>
<CommentForm />
<CommentList />
</React.Fragment>
);
}
export default Comment;

@ -0,0 +1,111 @@
$bdColor: rgba(244,244,244,1);
$bgColor: rgba(250,250,250,1);
$lh14: 14px;
$lh22: 22px;
$fz14: 14px;
$fz12: 12px;
$ml: 20px;
.comment_list_wrapper{
box-sizing: border-box;
border-top: 1px solid $bdColor;
.comment_item_area{
display: flex;
padding: 20px 0;
box-sizing: border-box;
border-bottom: 1px solid $bdColor;
.flex-image{
width: 48px;
height: 48px;
border-radius: 50%;
}
.item-desc{
flex: 1;
margin-left: $ml;
}
.item-header{
font-size: $fz14;
line-height: $lh14;
color: #333;
.item-time{
font-size: $fz12;
line-height: $lh14;
margin-left: $ml;
}
.item-close{
float: right;
cursor: pointer;
}
}
.item-ctx{
line-height: $lh22;
font-size: $fz12;
color: #333;
margin-top: 10px;
}
.comment_icon_area{
display: flex;
justify-content: flex-end;
margin-top: 10px;
.comment-icon-margin{
margin-left: 30px;
}
}
.comment_item_quill{
margin-top: 20px;
}
}
.comment_icon_count{
cursor: pointer;
font-size: 12px;
line-height: 1.5;
.comment_icon{
color: #333;
}
.comment_count{
color: #999999;
margin-left: 10px;
transition: color .3s;
}
&:hover{
.comment_icon,
.comment_count{
color: #5091FF;
}
}
}
.comment_item_append_list{
position: relative;
background-color: $bgColor;
border-radius: 5px;
padding: 0 15px 10px;
margin: 15px 0;
&::before {
position: absolute;
left: 15px;
bottom: 100%;
height: 0;
width: 0;
content: '';
// border: 5px solid transparent;
border: 10px solid transparent;
border-bottom-color: $bgColor;
}
.comment_item_loadmore{
padding-top: 10px;
cursor: pointer;
.loadmore-txt,
.loadmore-icon{
color: #999;
text-align: center;
font-size: $fz12;
}
}
}
}

@ -3,7 +3,7 @@
// export { default as OrderStateUtil } from '../routes/Order/components/OrderStateUtil';
export { getImageUrl as getImageUrl, getUrl as getUrl, getUrlmys as getUrlmys, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
, getUploadActionUrl as getUploadActionUrl,getUploadActionUrltwo as getUploadActionUrltwo , getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
, getUploadActionUrl as getUploadActionUrl,getUploadActionUrltwo as getUploadActionUrltwo ,getUploadActionUrlthree as getUploadActionUrlthree, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
, getTaskUrlById as getTaskUrlById, TEST_HOST ,htmlEncode as htmlEncode } from './UrlTool';
export { default as queryString } from './UrlTool2';

@ -0,0 +1,54 @@
/*
* @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';

@ -0,0 +1,47 @@
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;

@ -0,0 +1,169 @@
/*
* @Description: quill 编辑器
* @Author: tangjiang
* @Github:
* @Date: 2019-12-18 08:49:30
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-20 16:07:37
*/
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, useRef, useEffect } from 'react';
import Quill from 'quill';
import katex from 'katex';
import deepEqual from './deepEqual.js'
import { fetchUploadImage } from '../../services/ojService.js';
import { getImageUrl } from 'educoder'
import ImageBlot from './ImageBlot';
window.Quill = Quill;
window.katex = katex;
Quill.register(ImageBlot);
function QuillForEditor ({
placeholder,
readOnly,
options,
value,
imgAttrs = {}, // 指定图片的宽高
style = {},
wrapStyle = {},
showUploadImage,
onContentChange
}) {
// toolbar 默认值
const defaultConfig = [
['bold', 'italic', 'underline'],
[{align: []}, {list: 'ordered'}, {list: 'bullet'}], // 列表
[{script: 'sub'}, {script: 'super'}],
[{ 'color': [] }, { 'background': [] }],
[{header: [1,2,3,4,5,false]}],
['blockquote', 'code-block'],
['link', 'image', 'video'],
['formula'],
['clean']
];
const editorRef = useRef(null);
// quill 实例
const [quill, setQuill] = useState(null);
const [selection, setSelection] = useState(null);
// 文本内容变化时
const handleOnChange = content => {
// console.log('编辑器内容====》》》》', content);
onContentChange && onContentChange(content);
};
const renderOptions = options || defaultConfig;
// quill 配置信息
const quillOption = {
modules: {
toolbar: renderOptions
},
readOnly,
placeholder,
theme: readOnly ? 'bubble' : 'snow'
};
useEffect(() => {
const quillNode = document.createElement('div');
editorRef.current.appendChild(quillNode);
const _quill = new Quill(editorRef.current, quillOption);
setQuill(_quill);
// 处理图片上传功能
_quill.getModule('toolbar').addHandler('image', (e) => {
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 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
});
}
}
});
}, []);
// 设置值
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, setQuill]);
// 清除选择区域
useEffect(() => {
if (quill && selection) {
quill.setSelection(selection)
setSelection(null)
}
}, [quill, selection, setSelection]);
// 设置placeholder值
useEffect(() => {
if (!quill || !quill.root) return;
quill.root.dataset.placeholder = placeholder;
}, [quill, placeholder]);
// 处理内容变化
useEffect(() => {
if (!quill) return;
if (typeof handleOnChange !== 'function') return;
let handler;
quill.on(
'text-change',
(handler = () => {
handleOnChange(quill.getContents()); // getContents: 检索编辑器内容
})
);
return () => {
quill.off('text-change', handler);
}
}, [quill, handleOnChange]);
// 返回结果
return (
<div className='quill_editor_for_react_area' style={wrapStyle}>
<div ref={editorRef} style={style}></div>
</div>
);
}
export default QuillForEditor;

@ -0,0 +1,5 @@
.quill_editor_for_react_area{
.ql-editing{
left: 0 !important;
}
}

@ -0,0 +1,54 @@
/*
* @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';

@ -0,0 +1,45 @@
/*
* @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;

@ -0,0 +1,47 @@
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;

@ -0,0 +1,26 @@
/*
* @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;

@ -0,0 +1,108 @@
/*
* @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'));

@ -0,0 +1,32 @@
#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;
}
}

@ -0,0 +1,13 @@
/*
* @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 };

@ -0,0 +1,27 @@
/*
* @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;

@ -0,0 +1,148 @@
/*
* @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;

@ -0,0 +1,60 @@
/*
* @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;

@ -0,0 +1,33 @@
/*
* @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;

@ -0,0 +1,22 @@
/*
* @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;

@ -0,0 +1,31 @@
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

@ -866,6 +866,7 @@ class PollNew extends Component {
let newarr = [...arr];
newarr.splice(indexo, 1);
if(bool === true) {
console.log("shangchu1");
this.setState({
adddom: newarr,
q_countst: 0,
@ -878,6 +879,8 @@ class PollNew extends Component {
})
}else{
console.log("shangchu2");
this.setState({
adddom: newarr,
q_countst: 0,
@ -1416,6 +1419,7 @@ class PollNew extends Component {
}
if(bool === true){
console.log("tianjiadao1");
this.setState({
q_countst: 1,
bindingid:undefined,
@ -1423,6 +1427,7 @@ class PollNew extends Component {
newoption: false,
})
}else {
console.log("tianjiadao2");
this.setState({
q_countst: 1,
Newdisplay:false,
@ -2018,6 +2023,7 @@ class PollNew extends Component {
if (result.data.status === 0) {
this.props.showNotification(`已完成`);
thiss.thisinitializationdatanew();
console.log("已完成了了了1");
this.setState({
Newdisplay:false,
newoption: false,
@ -2109,6 +2115,7 @@ class PollNew extends Component {
axios.put(url,datay).then((result) => {
try {
if (result.data.status === 0) {
console.log("编辑题目成功1");
this.props.showNotification(`编辑题目成功`);
thiss.thisinitializationdatanew();
this.setState({
@ -3609,7 +3616,7 @@ class PollNew extends Component {
{
this.state.Newdisplay === true?
<div>
{this.state.adddom === undefined ? "" : this.state.adddom.map((itemo, indexo) => {
{this.state.adddom === undefined ? "publishtimeids123123" : this.state.adddom.map((itemo, indexo) => {
// console.log('打印this.state.adddom')
// console.log(this.state.adddom);
let arrid = itemo.question.id;

@ -1748,10 +1748,11 @@ class Listofworksstudentone extends Component {
} catch (e) {
}
// console.log("table1handleChange");
// console.log(sorter.columnKey);
try {
//学生成绩排序
if (sorter.columnKey === "finalscore") {
if (sorter.columnKey === "work_score") {
if (sorter.order === "ascend") {
//升序
this.setState({

@ -1,5 +1,5 @@
import React,{ Component } from "react";
import {Table, Pagination,Tooltip,Spin, Row, Col ,Checkbox,Tabs,Menu, Dropdown, Icon,Input} from "antd";
import {Table, Pagination,Popover,Spin, Row, Col ,Tabs, Icon} from "antd";
import { WordsBtn,on, off, trigger ,getImageUrl,sortDirections} from 'educoder';
import axios from'axios';
import Dropdownbox from './Dropdownbox';
@ -423,15 +423,38 @@ class Statistics extends Component{
:""
}
</React.Fragment>;
const content = (
<div className={"Statisticscircle"}>
<p>
课堂总成绩 * 70 %
</p>
<p>
课堂活跃度 * 10%
</p>
<p>
课外学习成绩 * 20%
</p>
<p>
其中课外学习成绩= 当前学生经验值 / 课堂学生经验值 最大值*100
</p>
</div>
);
return(
<React.Fragment>
<div className="edu-back-white">
<Spin size="large" spinning={this.state.topisSpin}>
<p className="clearfix padding30">
<Row gutter={24}>
<Col className={"Statisticsmxxy"}>
<Row>
<Col span={12}>
明星学员
</Col>
<Col span={12} className={"Statisticsliboxjsgz"}>
<span className={"mr10"}>计算规则</span>
<Popover placement="bottom" title={"明星学员计算说明"} content={content} trigger="hover">
<Icon type="info-circle" />
</Popover>
</Col>
</Row>
<Row type="flex" justify="center" align="bottom">

@ -16,7 +16,7 @@ import MultipTags from './components/multiptags';
// import { Link } from 'react-router-dom';
import CONST from '../../constants';
import { withRouter } from 'react-router';
import { toStore } from 'educoder';
import { toStore, CNotificationHOC } from 'educoder';
// import MyIcon from '../../common/components/MyIcon';
const {tagBackground, diffText} = CONST;
@ -249,17 +249,26 @@ class DeveloperHome extends React.PureComponent {
// 删除
handleClickDelete = (record) => {
const { deleteItem } = this.props;
Modal.confirm({
title: '删除',
this.props.confirm({
title: '提示',
content: `确定要删除${record.name}吗?`,
okText: '确定',
cancelText: '取消',
onOk () {
// 调用删除接口
console.log(record.identifier);
deleteItem(record.identifier);
}
});
// Modal.confirm({
// title: '删除',
// content: `确定要删除${record.name}吗?`,
// okText: '确定',
// cancelText: '取消',
// onOk () {
// // 调用删除接口
// console.log(record.identifier);
// deleteItem(record.identifier);
// }
// });
}
// table条件变化时
handleTableChange = (pagination, filters, sorter) => {
@ -562,5 +571,5 @@ const mapDispatchToProps = (dispatch) => ({
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(DeveloperHome));
)(CNotificationHOC() (DeveloperHome)));
// export default DeveloperHome;

@ -4,10 +4,10 @@
* @Github:
* @Date: 2019-11-27 16:02:36
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 17:32:33
* @LastEditTime: 2019-12-20 14:37:39
*/
import './index.scss';
import React, { useState, useRef } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { Tabs, Button, Icon } from 'antd';
import { connect } from 'react-redux';
import InitTabCtx from '../initTabCtx';
@ -23,14 +23,15 @@ const ControlSetting = (props) => {
submitLoading,
identifier,
excuteState,
commitRecordDetail,
showOrHideControl,
commitTestRecordDetail,
changeLoadingState,
changeSubmitLoadingStatus,
showOrHideControl,
changeShowOrHideControl,
// debuggerCode,
// startDebuggerCode, // 外部存入
onDebuggerCode,
updateCode,
// updateCode,
onSubmitForm
} = props;
const [defaultActiveKey, setDefaultActiveKey] = useState('1'); // 当前选中的tab
@ -44,10 +45,14 @@ const ControlSetting = (props) => {
setDefaultActiveKey(key);
}
useEffect(() => {
setShowTextResult(props.showOrHideControl);
}, [props]);
// 显示/隐藏tab
const handleShowControl = () => {
setShowTextResult(!showTextResult);
showOrHideControl(!showTextResult);
changeShowOrHideControl(!showTextResult);
}
// 调试代码
@ -55,7 +60,7 @@ const ControlSetting = (props) => {
// console.log(formRef.current.handleTestCodeFormSubmit);
// 调出控制台界面
setShowTextResult(true);
showOrHideControl(true);
changeShowOrHideControl(true);
formRef.current.handleTestCodeFormSubmit(() => {
setDefaultActiveKey('2');
});
@ -84,7 +89,7 @@ const ControlSetting = (props) => {
<Tabs
className={classNames}
activeKey={defaultActiveKey}
tabBarStyle={{ backgroundColor: 'rgba(48,48,48,1)', color: '#fff' }}
tabBarStyle={{ backgroundColor: 'rgba(18,28,36,1)', color: '#fff' }}
onChange={handleTabChange}
>
<TabPane tab={'自定义测试用例'} key={'1'} style={{ height: '280px', overflowY: 'auto' }}>
@ -97,7 +102,7 @@ const ControlSetting = (props) => {
<TabPane tab={'代码执行结果'} key={'2'} style={{ height: '280px', overflowY: 'auto' }}>
<ExecResult
excuteState={excuteState}
excuteDetail={commitRecordDetail}
excuteDetail={commitTestRecordDetail}
/>
</TabPane>
</Tabs>
@ -131,19 +136,20 @@ const ControlSetting = (props) => {
const mapStateToProps = (state) => {
const {commonReducer, ojForUserReducer} = state;
const {loading, excuteState, submitLoading } = commonReducer;
const { commitRecordDetail } = ojForUserReducer;
const {loading, excuteState, submitLoading, showOrHideControl } = commonReducer;
const { commitTestRecordDetail } = ojForUserReducer;
return {
loading,
submitLoading,
excuteState,
showOrHideControl,
// identifier: user_program_identifier,
commitRecordDetail // 提交详情
commitTestRecordDetail // 提交详情
};
};
// changeSubmitLoadingStatus
const mapDispatchToProps = (dispatch) => ({
showOrHideControl: (flag) => dispatch(actions.showOrHideControl(flag)),
changeShowOrHideControl: (flag) => dispatch(actions.changeShowOrHideControl(flag)),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag)),
changeSubmitLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag)),
debuggerCode: (identifier, values) => dispatch(actions.debuggerCode(identifier, values)),

@ -2,7 +2,8 @@
position: absolute;
bottom: 0;
width: 100%;
background:rgba(30,30,30,1);
// background: red;
// background:rgba(30,30,30,1);
// height: 56px;
.control_tab{
position: absolute;
@ -50,10 +51,10 @@
align-items: center;
z-index: 20;
height: 56px;
padding-right: 30px;
padding-right: 20px;
padding-left: 10px;
// background: #000;
background:rgba(48,48,48,1);
background: rgba(18,28,36,1);
// background:rgba(48,48,48,1);
}
.setting_drawer{

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-28 08:44:54
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 09:24:02
* @LastEditTime: 2019-12-19 10:44:16
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
@ -38,6 +38,15 @@ function ExecResult (props) {
</span>
</div>
);
const renderError = () => (
<div className={'excute_result_area excute_flex_center'}>
<span className={'loaded_ctx'}>
<span>未知异常</span>
</span>
</div>
)
const renderFinish = () => {
const {
error_line,
@ -60,6 +69,7 @@ function ExecResult (props) {
)
}
// console.log('执行结果====》》》》', status);
const excuteCtx = (state) => {
if (state === 0) {
return (
@ -118,6 +128,8 @@ function ExecResult (props) {
setRenderCtx(() => (readerLoaded));
} else if ('finish' === excuteState) {
setRenderCtx(() => (renderFinish));
} else if ('error' === excuteState) {
setRenderCtx(() => (renderError))
}
}, [excuteState]);

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 19:46:14
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 17:38:42
* @LastEditTime: 2019-12-19 10:47:05
*/
import './index.scss';
import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
@ -26,6 +26,7 @@ function InitTabCtx (props, ref) {
const { inputValue, onDebuggerCode } = props;
console.log('default value', inputValue);
useImperativeHandle(ref, () => ({
handleTestCodeFormSubmit: (cb) => {
// console.log('父组件调用我啦~~~~~~~~~');
@ -33,6 +34,10 @@ function InitTabCtx (props, ref) {
}
}));
useEffect(() => {
console.log('初始值: ========', props);
}, [props]);
// 渲染文本提示信息
const renderText = () => (<span className={'ctx_default'}>请在这里添加测试用例点击调试代码时将从这里读取输入来测试你的代码...</span>);
// 渲染表单信息

@ -49,7 +49,8 @@
}
.input_textarea_style{
background:rgba(30,30,30,1) !important;
// background:rgba(30,30,30,1) !important;
background:rgba(7,15,25,1) !important;
color: #fff;
border-color: transparent;
outline: none;

@ -4,10 +4,11 @@
* @Github:
* @Date: 2019-11-25 17:50:33
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-06 16:51:48
* @LastEditTime: 2019-12-19 19:32:08
*/
import React, { useState } from 'react';
import { fromStore, toStore } from 'educoder';
// import { Icon } from 'antd';
// import { Select } from 'antd';
// const { Option } = Select;
const SettingDrawer = (props) => {

@ -4,12 +4,12 @@
* @Github:
* @Date: 2019-11-27 15:02:52
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 16:16:56
* @LastEditTime: 2019-12-20 20:07:11
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
import { Drawer, Modal } from 'antd';
import { fromStore } from 'educoder';
import { Drawer, Tooltip, Badge } from 'antd';
import { fromStore, CNotificationHOC } from 'educoder';
import { connect } from 'react-redux';
import MonacoEditor from '@monaco-editor/react';
import SettingDrawer from '../../components/monacoSetting';
@ -19,16 +19,25 @@ import MyIcon from '../../../../common/components/MyIcon';
// import actions from '../../../../redux/actions';
const { fontSetting, opacitySetting } = CONST;
const maps = {
'c': 'main.c',
'c++': 'main.cc',
'java': 'main.java',
'pythone': 'main.py'
};
function MyMonacoEditor (props, ref) {
const {
notice,
language,
identifier,
hadCodeUpdate,
showOrHideControl,
// saveUserInputCode,
onCodeChange,
onRestoreInitialCode
onRestoreInitialCode,
onUpdateNotice
} = props;
const [showDrawer, setShowDrawer] = useState(false); // 控制配置滑框
@ -50,7 +59,7 @@ function MyMonacoEditor (props, ref) {
}, [props]);
useEffect(() => {
setHeight(showOrHideControl ? 'calc(100% - 382px)' : 'calc(100% - 56px)');
setHeight(showOrHideControl ? 'calc(100% - 378px)' : 'calc(100% - 56px)');
}, [showOrHideControl]);
// 控制侧边栏设置的显示
@ -93,29 +102,80 @@ function MyMonacoEditor (props, ref) {
// 恢复初始代码
const handleRestoreCode = () => {
Modal.confirm({
props.confirm({
title: '提示',
content: '确定要恢复代码吗?',
okText: '确定',
cancelText: '取消',
onOk () {
onRestoreInitialCode && onRestoreInitialCode();
}
})
// Modal.confirm({
// content: '确定要恢复代码吗?',
// okText: '确定',
// cancelText: '取消',
// onOk () {
// onRestoreInitialCode && onRestoreInitialCode();
// }
// })
}
const handleUpdateNotice = () => {
if (props.notice) {
onUpdateNotice && onUpdateNotice();
}
}
const renderRestore = identifier ? (
<MyIcon type="iconzaicizairu" />
) : '';
// lex_has_save ${hadCodeUpdate} ? : ''
const _classnames = hadCodeUpdate ? `flex_strict flex_has_save` : 'flex_strict';
return (
<React.Fragment>
<div className={"monaco_editor_area"}>
<div className="code_title">
{/* 未保存时 ? '学员初始代码文件' : main.x */}
<span className='flex_strict' style={{ color: '#fff'}}>{identifier ? '' : '学员初始代码文件'}</span>
<span className='flex_strict'>{identifier ? '已保存' : ''}</span>
<span onClick={handleRestoreCode} className="flex_normal">{renderRestore}</span>
{/* <Icon className={'code-icon'} type="setting" onClick={handleShowDrawer}/> */}
<MyIcon className='code-icon' type="iconshezhi" onClick={handleShowDrawer}/>
<span className='flex_strict' style={{ color: '#ddd'}}>{identifier ? language ? maps[language.toLowerCase()] : '' : '学员初始代码文件'}</span>
<span className={_classnames}>{hadCodeUpdate ? '已保存' : ''}</span>
{/* <Tooltip
style={{ background: 'gold' }}
className="tooltip_style"
title="通知"
placement="bottom"
> */}
<Tooltip
placement="bottom"
title="通知"
>
<Badge
className="flex_normal"
style={{ color: '#666'}}
dot={notice}
onClick={handleUpdateNotice}
>
{/* <Icon type="bell" /> */}
<MyIcon type="iconxiaoxi1" />
</Badge>
</Tooltip>
<Tooltip
placement="bottom"
title="恢复"
>
<MyIcon
className="flex_normal"
onClick={handleRestoreCode}
type="iconzaicizairu"
style={{ display: identifier ? 'inline-block' : 'none'}}
/>
{/* <span onClick={handleRestoreCode} className="flex_normal" style={{ display: identifier ? 'inline-block' : 'none'}}>{renderRestore}</span> */}
</Tooltip>
<Tooltip
placement="bottom"
title="设置"
>
<MyIcon className='code-icon' type="iconshezhi" onClick={handleShowDrawer}/>
</Tooltip>
</div>
<MonacoEditor
height={height}
@ -131,7 +191,6 @@ function MyMonacoEditor (props, ref) {
<Drawer
className={'setting_drawer'}
placement="right"
closable={false}
onClose={handleDrawerClose}
visible={showDrawer}
>
@ -161,4 +220,4 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyMonacoEditor);
)(CNotificationHOC() (MyMonacoEditor));

@ -1,23 +1,21 @@
.monaco_editor_area{
height: 100%;
background-color: rgba(30,30,30,1);
background-color: rgba(7,15,25,1);
.code_title{
display: flex;
align-items: center;
// justify-content: space-between;
// background: #000;
// background: #333333;
background-color: rgba(48,48,48,1);
background-color: rgba(18,28,36,1);
color: #fff;
height: 56px;
padding: 0 30px;
padding: 0 20px;
.flex_strict{
flex: 1;
}
.flex_normal{
color: #E51C24;
cursor: pointer;
margin-right: 30px;
margin-right: 20px;
}
.code-icon{
cursor: pointer;
@ -25,21 +23,21 @@
.flex_strict,
.flex_normal,
.code-icon{
color: #888;
color: #666;
}
}
}
.setting_drawer{
// .ant-drawer-body{
// // height: calc(100vh - 120px);
// // overflow-y: auto;
// }
.ant-drawer-close{
color: #ffffff;
}
.ant-drawer-content{
top: 120px;
bottom: 56px;
height: calc(100vh - 176px);
background: #333333;
// background: #333333;
background: rgba(7,15,25,1);
color: #fff;
.setting_h2{
color: #fff;
@ -57,4 +55,17 @@
color: #fff;
}
}
}
.flex_has_save{
// animation: blink 3s line 3;
animation-name: blink;
animation-duration: .4s;
animation-iteration-count: 3;
}
@keyframes blink{
50% {
color: #fff;
}
}

@ -3,6 +3,7 @@
position: absolute;
color: #fff;
line-height: 65px;
left: 20px;
// height: 65px;
.student_img,
.student_nicker{

@ -13,11 +13,11 @@ import { Button, Modal } from 'antd';
import LeftPane from './leftpane';
import RightPane from './rightpane';
import { withRouter } from 'react-router';
import { toStore } from 'educoder';
import { toStore, CNotificationHOC } from 'educoder';
import UserInfo from '../components/userInfo';
// import RightPane from './rightpane/index';
import actions from '../../../redux/actions';
import {ModalConfirm} from '../../../common/components/ModalConfirm';
// import {ModalConfirm} from '../../../common/components/ModalConfirm';
const NewOrEditTask = (props) => {
const {
@ -69,11 +69,14 @@ const NewOrEditTask = (props) => {
// 模拟挑战
const imitationChallenge = () => {
// 调用 start 接口, 成功后跳转到模拟页面
identifier && startProgramQuestion(identifier, props);
}
// 开始挑战
const startChallenge = () => {
// 调用 start 接口, 成功后跳转到模拟页面
startProgramQuestion(identifier, props);
// 调用 start 接口, 成功后跳转到开启实战
// TODO
identifier && startProgramQuestion(identifier, props);
}
// 取消
@ -82,27 +85,38 @@ const NewOrEditTask = (props) => {
props.clearOJFormStore();
// 清空描述信息
toStore('oj_description', '');
setInterval(function () {
props.history.push('/problems');
}, 500);
props.history.push('/problems');
}
// 发布
const handleClickPublish = () => {
ModalConfirm('提示', (<p>发布后即可应用到自己管理的课堂<br /> 是否确认发布?</p>), () => {
changePublishLoadingStatus(true);
handlePublish(props, 'publish');
// ModalConfirm('提示', (<p>发布后即可应用到自己管理的课堂<br /> 是否确认发布?</p>), () => {
// changePublishLoadingStatus(true);
// handlePublish(props, 'publish');
// });
props.confirm({
title: '提示',
content: (<p>发布后即可应用到自己管理的课堂<br /> 是否确认发布?</p>),
onOk () {
changePublishLoadingStatus(true);
handlePublish(props, 'publish');
}
});
}
// 撤销发布
const handleClickCancelPublish = () => {
ModalConfirm('提示', (<p>是否确认撤销发布?</p>), () => {
changePublishLoadingStatus(true);
handleCancelPublish(props, identifier);
// ModalConfirm('提示', (<p>是否确认撤销发布?</p>), () => {
// changePublishLoadingStatus(true);
// handleCancelPublish(props, identifier);
// });
props.confirm({
title: '提示',
content: ((<p>是否确认撤销发布?</p>)),
onOk () {
changePublishLoadingStatus(true);
handleCancelPublish(props, identifier);
}
});
}
// 取消保存/取消按钮
@ -125,6 +139,7 @@ const NewOrEditTask = (props) => {
const renderPubOrFight = () => {
const pubButton = isPublish
? (<Button
style={{ background: 'rgba(102,102,102,1)', border: 'none' }}
type="primary"
loading={publishLoading}
onClick={handleClickCancelPublish}
@ -141,39 +156,40 @@ const NewOrEditTask = (props) => {
<Button type="primary" onClick={imitationChallenge}>模拟挑战</Button>
);
// 更新
// const updateBtn = isPublish
// ? ''
// : (
// <Button
// type="primary"
// loading={submitLoading}
// onClick={handleSubmitForm}
// >更新</Button>
// );
return (
<React.Fragment>
<Button
type="primary"
loading={submitLoading}
onClick={handleSubmitForm}
>更新</Button>
{pubButton}
{challengeBtn}
</React.Fragment>
)
if (isPublish) {
return (
<React.Fragment>
{pubButton}
<Button
type="primary"
loading={submitLoading}
onClick={handleSubmitForm}
>保存</Button>
{challengeBtn}
</React.Fragment>
);
} else {
return (
<React.Fragment>
<Button
type="primary"
loading={submitLoading}
onClick={handleSubmitForm}
>保存</Button>
{pubButton}
{challengeBtn}
</React.Fragment>
);
}
}
// 渲染退出
const renderQuit = () => {
return identifier ? (
<Button type="link"
style={{
position: 'absolute',
right: '10px',
top: '15px',
color: '#5091FF'
}}
icon='poweroff'
className='quite_btn'
onClick={handleClickCancel}
>退出</Button>
) : ''
@ -255,4 +271,4 @@ const mapDispatchToProps = (dispatch) => ({
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(NewOrEditTask));
)(CNotificationHOC() (NewOrEditTask)));

@ -10,9 +10,23 @@
align-items: center;
justify-content: center;
height: 56px;
background: #333333;
// background: #333333;
background: rgba(18,28,36,1);
> button{
margin-right: 20px;
}
}
.quite_btn{
position: absolute;
right: 10px;
top: 15px;
margin-left: 30px;
color: #888888;
transition: all .3s;
cursor: pointer;
&:hover{
color: #5091FF;
}
}

@ -4,42 +4,50 @@
* @Github:
* @Date: 2019-11-21 09:19:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 11:58:46
* @LastEditTime: 2019-12-20 17:32:10
*/
import './index.scss';
import React, { useState } from 'react';
import { Collapse, Icon, Input, Form, Button, Modal } from 'antd';
import React from 'react';
import { Collapse, Icon, Input, Form } from 'antd';
import { connect } from 'react-redux';
import actions from '../../../../../redux/actions';
import { CNotificationHOC} from 'educoder';
const { Panel } = Collapse;
const { TextArea } = Input;
const FormItem = Form.Item;
const AddTestDemo = (props) => {
const {
key,
onSubmitTest,
// onSubmitTest,
onDeleteTest,
testCase,
testCaseValidate,
isOpen
} = props;
const [isEditor, setIsEditor] = useState(false); // 是否是编辑
// const [isEditor, setIsEditor] = useState(false); // 是否是编辑
// 删除操作
const handleDeletePanel = (e) => {
// console.log('点击的删除按钮')
e.preventDefault();
e.stopPropagation();
Modal.confirm({
title: '删除',
props.confirm({
title: '提示',
content: '确定要删除当前测试用例吗?',
okText: '确定',
cancelText: '取消',
onOk() {
onDeleteTest(testCase);
}
})
});
// Modal.confirm({
// title: '删除',
// content: '确定要删除当前测试用例吗?',
// okText: '确定',
// cancelText: '取消',
// onOk() {
// onDeleteTest(testCase);
// }
// })
}
// 输入框值改变时
@ -60,59 +68,60 @@ const AddTestDemo = (props) => {
const genExtra = () => (
<Icon
type="close"
className="collapse_close_icon"
onClick={handleDeletePanel}
/>
)
// 取消操作
const handleReset = (e) => {
e.preventDefault();
props.form.resetFields();
}
// const handleReset = (e) => {
// e.preventDefault();
// props.form.resetFields();
// }
// 保存
const handleSubmit = (e) => {
e.preventDefault();
props.form.validateFields((err, values) => {
if (err) {
return;
}
console.log('提交表单: ', values);
onSubmitTest(values);
});
}
// const handleSubmit = (e) => {
// e.preventDefault();
// props.form.validateFields((err, values) => {
// if (err) {
// return;
// }
// console.log('提交表单: ', values);
// onSubmitTest(values);
// });
// }
// 编辑后保存
const handleEditorOrSave = (e) => {
if (!isEditor) {
setIsEditor(true);
} else {
// TODO 调用修改测试用例接口
setIsEditor(false); // 保存后 设置 false
}
}
// const handleEditorOrSave = (e) => {
// if (!isEditor) {
// setIsEditor(true);
// } else {
// // TODO 调用修改测试用例接口
// setIsEditor(false); // 保存后 设置 false
// }
// }
// 渲染提交按钮
const renderSubmitBtn = () => {
const { identifier, testCase, loading } = props;
// console.log('========', identifier);
// 1. 新增时,不显示按钮
if (identifier) {
if (testCase.isAdd) {
return (
<FormItem style={{ textAlign: 'right' }}>
<Button style={{ marginRight: '20px' }} onClick={handleReset}>取消</Button>
<Button type="primary" onClick={handleSubmit}>保存</Button>
</FormItem>
);
} else {
return (
<FormItem style={{ textAlign: 'right' }}>
<Button onClick={handleEditorOrSave} loading={loading}>{isEditor ? '保存' : (loading ? '保存' : '编辑')}</Button>
</FormItem>
);
}
}
}
// const renderSubmitBtn = () => {
// const { identifier, testCase, loading } = props;
// // console.log('========', identifier);
// // 1. 新增时,不显示按钮
// if (identifier) {
// if (testCase.isAdd) {
// return (
// <FormItem style={{ textAlign: 'right' }}>
// <Button style={{ marginRight: '20px' }} onClick={handleReset}>取消</Button>
// <Button type="primary" onClick={handleSubmit}>保存</Button>
// </FormItem>
// );
// } else {
// return (
// <FormItem style={{ textAlign: 'right' }}>
// <Button onClick={handleEditorOrSave} loading={loading}>{isEditor ? '保存' : (loading ? '保存' : '编辑')}</Button>
// </FormItem>
// );
// }
// }
// }
/**
* 文本输入框可编辑的情况
@ -120,9 +129,9 @@ const AddTestDemo = (props) => {
* 2. isAdd false isEditor 为true
* @param {*} testCase
*/
const isDisabled = (testCase) => {
return !testCase.isAdd && !isEditor;
};
// const isDisabled = (testCase) => {
// return !testCase.isAdd && !isEditor;
// };
// const {input = {}, output = {}} = (testCasesValidate[index] = {});
const activePane = {
@ -150,7 +159,8 @@ const AddTestDemo = (props) => {
rows={5}
value={testCase.input}
onChange={handleInputChange}
disabled={isDisabled(testCase)}/>
// disabled={isDisabled(testCase)}
/>
</FormItem>
<FormItem
label={<span className={'label_text'}>输出</span>}
@ -162,9 +172,10 @@ const AddTestDemo = (props) => {
rows={5}
value={testCase.output}
onChange={handleOutputChange}
disabled={isDisabled(testCase)}/>
// disabled={isDisabled(testCase)}
/>
</FormItem>
{renderSubmitBtn()}
{/* {renderSubmitBtn()} */}
</Form>
</Panel>
</Collapse>
@ -189,4 +200,4 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form.create()(AddTestDemo));
)(Form.create()(CNotificationHOC()(AddTestDemo)));

@ -4,38 +4,40 @@
* @Github:
* @Date: 2019-11-20 10:35:40
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 11:39:52
* @LastEditTime: 2019-12-20 16:53:55
*/
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.bubble.css';
import 'quill/dist/quill.snow.css';
import './index.scss';
// import 'katex/dist/katex.css';
import React from 'react';
import { Form, Input, Select, InputNumber, Button } from 'antd';
import { connect } from 'react-redux';
import AddTestDemo from './AddTestDemo';
import QuillEditor from '../../../quillEditor';
// import QuillEditor from '../../../quillEditor';
import actions from '../../../../../redux/actions';
import CONST from '../../../../../constants';
import { fromStore, toStore } from 'educoder'; // 保存和读取store值
import { toStore } from 'educoder'; // 保存和读取store值
// import Wrapper from '../../../../../common/reactQuill';
import QuillForEditor from '../../../../../common/quillForEditor';
const scrollIntoView = require('scroll-into-view');
const {jcLabel} = CONST;
const FormItem = Form.Item;
const { Option } = Select;
const maps = {
language: [
{ title: (<span style={{ color: 'rgba(0, 0, 0, 0.35)' }}>请选择</span>), key: '' },
{ title: 'C', key: 'C' },
// { title: 'C++', key: 'C++' },
// { title: 'Python', key: 'Python' },
// { title: 'Java', key: 'Java' }
{ title: 'C++', key: 'C++' },
{ title: 'Python', key: 'Python' },
{ title: 'Java', key: 'Java' }
],
difficult: [
{ title: (<span style={{ color: 'rgba(0, 0, 0, 0.35)' }}>请选择</span>), key: '' },
{ title: '简单', key: '1' },
{ title: '中等', key: '2'},
{ title: '困难', key: '3' }
],
category: [
{ title: (<span style={{ color: 'rgba(0, 0, 0, 0.35)' }}>请选择</span>), key: '' },
{ title: '程序设计', key: '1' },
{ title: '算法', key: '2'}
],
@ -146,9 +148,6 @@ class EditTab extends React.Component {
testCasesValidate,
openTestCodeIndex = []
} = this.props;
// console.log('当前位置: ', position);
// console.log('OJForm: ', ojForm);
// console.log('当前位置: ', testCases);
// 表单label
const myLabel = (name, subTitle) => {
if (subTitle) {
@ -185,7 +184,6 @@ class EditTab extends React.Component {
};
const renderTestCase = () => {
return this.props.testCases.map((item, i) => {
console.log(111);
return <AddTestDemo
key={`${i}`}
isOpen={openTestCodeIndex.includes(i)}
@ -222,17 +220,25 @@ class EditTab extends React.Component {
// TODO 点击新增时,需要滚到到最底部
this.scrollToBottom();
}
// 描述信息变化时
const handleContentChange = (content) => {
console.log('描述信息为: ', content);
// 保存获取的描述信息至redux中
this.handleChangeDescription(content);
}
// 编辑器配置信息
const quillConfig = [
[{ header: [1, 2, 3, 4, 5, 6, false] }],
['bold', 'italic', 'underline', 'strike'], // 切换按钮
['blockquote', 'code-block'], // 代码块
[{ 'list': 'ordered' }, { 'list': 'bullet' }], // 列表
[{align: []}, { 'list': 'ordered' }, { 'list': 'bullet' }], // 列表
[{ 'script': 'sub'}, { 'script': 'super' }],
[{ 'color': [] }, { 'background': [] }], // 字体颜色与背景色
['formula', 'image', 'video'], // 数学公式、图片、视频
['image', 'formula'], // 数学公式、图片、视频
['clean'], // 清除格式
];
return (
<div className={'editor_area'} id="textCase">
<Form className={'editor_form'}>
@ -267,7 +273,7 @@ class EditTab extends React.Component {
help={ojFormValidate.timeLimit.errMsg}
colon={ false }
>
<InputNumber value={ojForm.timeLimit} min={0} style={{ width: '100%' }} onChange={this.handleTimeLimitChange}/>
<InputNumber value={ojForm.timeLimit} min={0} max={5} style={{ width: '100%' }} onChange={this.handleTimeLimitChange}/>
</FormItem>
<FormItem
@ -305,13 +311,16 @@ class EditTab extends React.Component {
help={ojFormValidate.description.errMsg}
colon={ false }
>
<QuillEditor
style={{ height: '300px' }}
placeholder="请输入描述信息"
onEditorChange={this.handleChangeDescription}
htmlCtx={ojForm.description || fromStore('oj_description')}
options={quillConfig}
/>
<div style={{ marginTop: '15px'}}>
<QuillForEditor
style={{ height: '200px' }}
placeholder="请输入描述信息"
onContentChange={handleContentChange}
options={quillConfig}
value={ojForm.description}
/>
</div>
</FormItem>
{/* <FormItem
@ -325,6 +334,7 @@ class EditTab extends React.Component {
{getOptions('openOrNot')}
</Select>
</FormItem> */}
</Form>
{/* 添加测试用例 */}

@ -48,7 +48,12 @@
.test_demo_title,
.test_demo_ctx,
.editor_form{
margin: 0 30px;
margin: 0 20px;
.ant-form-explain{
margin-top: 5px;
margin-left: -10px;
}
}
.test_demo_title{
display: flex;
@ -61,9 +66,9 @@
&.fix_top{
position: absolute;
top: 43px;
left: -30px;
right: -30px;
padding: 0 30px;
left: -20px;
right: -20px;
padding: 0 26px 0 20px;
// background: gold;
background: rgb(249,249,249);
z-index: 1000;
@ -72,5 +77,27 @@
.collapse_area{
margin-bottom: 20px;
.ant-form-item{
margin-bottom: 0px;
}
}
}
.collapse_close_icon{
position: relative;
background: rgba(235, 235, 235, 1);
border-radius: 50%;
font-size: 12px;
padding: 5px 5px;
color: rgb(142, 142, 142);
transition: all .3s;
&:hover{
color: #fff;
background: rgb(231, 81, 79);
}
// &:hover{
// color: red;
// }
}

@ -34,28 +34,6 @@ function LeftPane (props) {
const [defaultActiveKey, setDefaultActiveKey] = useState('editor');
// const tabArrs = [
// { title: '编辑', key: 'editor', content: (<EditorTab />) },
// { title: '预览', key: 'prev', content: (<PrevTab />) },
// // { title: '提交记录', key: 'commit', content: (<CommitTab />) },
// ];
// const tabs = tabArrs.map((tab) => {
// const Comp = tab.content;
// return (
// <TabPane tab={tab.title} key={tab.key}>
// { Comp }
// </TabPane>
// )
// });
// tab切换时
// const handleTabChange = (key) => {
// setDefaultActiveKey(key);
// }
// 执行表单提交函数
const renderComp = useMemo(() => {
return Comp[defaultActiveKey];
}, [defaultActiveKey]);

@ -4,74 +4,47 @@
* @Github:
* @Date: 2019-11-24 10:09:55
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-04 23:38:37
* @LastEditTime: 2019-12-18 10:02:24
*/
import './index.scss';
import React, { useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux';
import {Empty} from 'antd';
// import QuillEditor from '../../../quillEditor';
const Quill = window.Quill;
// import Wrapper from '../../../../../common/reactQuill';
import QuillForEditor from '../../../../../common/quillForEditor';
const PrevTab = (props) => {
const {
description
} = props;
const prevRef = useRef(null);
const [desc, setDesc] = useState('');
const [renderCtx, setRenderCtx] = useState(() => {
return function () {
return '';
}
});
// 空内容
const renderTxt = () => (
<div className='no_result'>
<Empty />
</div>
);
// const [desc, setDesc] = useState('');
const [renderCtx, setRenderCtx] = useState(() => '');
// 渲染内容
const renderQuill = () => (
<div
id="quill_editor"
style = {{ height: '100%', width: '100%'}}
ref={prevRef}>
</div>
);
useEffect(() => {
setDesc(description);
}, [description]);
useEffect(() => {
if (description) {
setRenderCtx(() => renderQuill);
let count = 0;
let timer = setInterval(() => {
count++;
if (count >= 10 || prevRef.current) {
clearInterval(timer);
timer = null;
if (prevRef.current) {
const quillEditor = new Quill(prevRef.current, {
readOnly: true,
theme: 'bubble'
});
quillEditor.container.firstChild.innerHTML = description;
}
}
}, 50);
if (props.description) {
setRenderCtx(() => (
<div
id="quill_editor"
style = {{ height: '100%', width: '100%'}}
ref={prevRef}>
<QuillForEditor
readOnly={true}
value={props.description}
/>
</div>
));
} else {
setRenderCtx(() => renderTxt);
setRenderCtx(() => (
<div className='no_result'>
<Empty />
</div>
));
}
}, [description]);
}, [props]);
return (
<div className={`prev_area`}>
{renderCtx()}
{renderCtx}
</div>
)

@ -1,7 +1,7 @@
.right_pane_code_wrap{
position: relative;
// justify-content: center;
background-color: #222;
// background-color: #222;
height: 100%;
// height: calc(100vh - 178px);
.code-title,
@ -11,12 +11,13 @@
align-items: center;
justify-content: space-between;
// padding: 0 30px;
background: #000;
// background: #000;
background: rgba(18,28,36,1);
color: #fff;
}
.code-title,
.pane_control_opts{
padding: 0 30px;
padding: 0 20px;
}
.code-title{

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-12-04 08:36:21
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 18:55:02
* @LastEditTime: 2019-12-20 20:05:57
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
@ -37,8 +37,7 @@ function RecordDetail (props) {
useEffect(() => {
// 根据id获取记录详情
console.log('提交记录详情', recordDetail);
getUserCommitRecordDetail(id, 'detail');
getUserCommitRecordDetail(id, 'detail');
}, []);
useEffect(() => {
@ -68,7 +67,7 @@ function RecordDetail (props) {
<span>{detail.name || 'test'}</span>
</div>
<div className={'study_quit'}>
<Button>
<Button style={{ visibility: identifier ? 'visible' : 'hidden'}}>
<Link to={`/myproblems/${identifier}`}>返回该题</Link>
</Button>
</div>
@ -79,7 +78,7 @@ function RecordDetail (props) {
</div>
<div className="detail_ctx_status">
<span className="status_label">
状态: <span className="status_label_error">{reviewResult[detail.status]}</span>
状态: <span className={detail.status === 0 ? 'status_label_success' : 'status_label_error'}>{reviewResult[detail.status]}</span>
</span>
<span className="status_label">
提交时间: <span className="status_label_sub">
@ -87,7 +86,10 @@ function RecordDetail (props) {
</span>
</span>
<span className="status_label">
语言: <span className="status_label_sub">C</span>
语言: <span className="status_label_sub">{detail.language}</span>
</span>
<span className="status_label" style={{ visibility: detail.status === 0 ? 'visible' : 'hidden'}}>
执行用时: <span className="status_label_sub">{`${detail.execute_time && (+detail.execute_time * 1000)}ms`}</span>
</span>
</div>
<div className="result_error_area">
@ -96,6 +98,7 @@ function RecordDetail (props) {
<div className="detail_ctx_header">
<h2 className="header_h2">提交内容</h2>
<Button
style={{ visibility: identifier ? 'visible' : 'hidden'}}
className={'header_btn'}
type="primary"
>

@ -2,7 +2,7 @@
.record_detail_area{
.record_detail_ctx{
padding: 0 30px;
padding: 0 20px;
.detail_ctx_header{
position: relative;
height: 56px;

@ -8,8 +8,9 @@
.record_detail_header{
height: 65px;
// background:rgba(34,34,34,1);
background: #1E1E1E;
padding:0 30px;
// background: #1E1E1E;
background: rgba(7,15,25,1);
padding:0 20px;
}
.task_header{
@ -87,7 +88,7 @@
.add_editor_list_area{
background: #fff;
padding: 0 30px;
padding: 0 20px;
margin: 0;
.add_editor_item{
display: inline-block;
@ -123,7 +124,8 @@
.split-pane-area,
.split-pane-left{
.ant-tabs-nav-wrap{
padding: 0 30px;
// padding: 0 30px;
padding: 0 20px;
}
.ant-tabs-bar{
margin: 0;

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-23 10:53:19
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 17:19:15
* @LastEditTime: 2019-12-20 14:54:39
*/
import './index.scss';
import React, { useEffect, useState } from 'react';
@ -15,10 +15,10 @@ import RightPane from './rightpane';
// import { Link } from 'react-router-dom';
// import { getImageUrl } from 'educoder'
// import RightPane from '../newOrEditTask/rightpane';
import { Icon, Modal } from 'antd';
import { Icon } from 'antd';
import UserInfo from '../components/userInfo';
import actions from '../../../redux/actions';
import { fromStore} from 'educoder';
import { fromStore, CNotificationHOC} from 'educoder';
import { withRouter } from 'react-router';
function StudentStudy (props) {
@ -29,7 +29,8 @@ function StudentStudy (props) {
userInfo,
hack_identifier,
// user_program_identifier,
restoreInitialCode
restoreInitialCode,
changeShowOrHideControl
} = props;
const {
@ -51,32 +52,54 @@ function StudentStudy (props) {
const { hack = {} } = props;
if (hack.modify_code && hasUpdate) { // 代码更改,提示是否需要更新代码
setHasUpdate(false);
Modal.confirm({
title: '提示',
content: (
<p>
代码文件有更新啦 <br />
还未提交的代码请自行保存
</p>
),
okText: '立即更新',
cancelText: '稍后再说',
onOk () {
restoreInitialCode(id, '更新成功');
}
});
handleUpdateNotice();
}
}, [props, hasUpdate, setHasUpdate]);
const handleUpdateNotice = () => {
console.log(props);
props.confirm({
title: '提示',
content: (
<p>
代码文件有更新啦 <br />
还未提交的代码请自行保存
</p>
),
onOk () {
restoreInitialCode(id, '更新成功');
}
})
// Modal.confirm({
// title: '提示',
// content: (
// <p>
// 代码文件有更新啦 <br />
// 还未提交的代码,请自行保存
// </p>
// ),
// okText: '立即更新',
// cancelText: '稍后再说',
// onOk () {
// restoreInitialCode(id, '更新成功');
// }
// });
}
const _hack_id = hack_identifier || fromStore('hack_identifier');
// 处理编辑
const handleClickEditor = () => {
changeShowOrHideControl(false);
props.saveEditorCodeForDetail();
props.history.push(`/problems/${_hack_id}/edit`);
props.clearOjForUserReducer();
}
// 处理退出
const handleClickQuit = () => {
// 退出时,清空内容
props.clearOjForUserReducer();
// 将控制台关闭
changeShowOrHideControl(false);
props.saveEditorCodeForDetail();
props.history.push('/problems');
}
@ -96,7 +119,11 @@ function StudentStudy (props) {
</div>
<div className={'study_quit'}>
{/* to={`/problems/${_hack_id}/edit`} */}
<span onClick={handleClickEditor} className={`quit-btn`}>
<span
style={{ display: userInfo.hack_manager ? 'inline-block' : 'none' }}
onClick={handleClickEditor}
className={`quit-btn`}
>
<Icon type="form" className="quit-icon"/> 编辑
</span>
{/* to="/problems" */}
@ -117,7 +144,9 @@ function StudentStudy (props) {
<LeftPane />
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane />
<RightPane
updateNotice={handleUpdateNotice}
/>
<div />
</SplitPane>
</SplitPane>
@ -146,11 +175,13 @@ const mapDispatchToProps = (dispatch) => ({
saveEditorCodeForDetail: (code) => dispatch(actions.saveEditorCodeForDetail(code)),
// 恢复初始代码
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
changeShowOrHideControl: (flag) => dispatch(actions.changeShowOrHideControl(flag)),
clearOjForUserReducer: () => dispatch(actions.clearOjForUserReducer())
});
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(StudentStudy));
)(CNotificationHOC()(StudentStudy)));

@ -5,6 +5,6 @@
.right_pane_code_wrap{
position: relative;
background-color: #222;
// background-color: #222;
height: 100%;
}

@ -4,15 +4,18 @@
* @Github:
* @Date: 2019-11-27 09:49:35
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 09:52:53
* @LastEditTime: 2019-12-17 17:46:05
*/
import './index.scss';
import React from 'react';
const Comment = (props) => {
import Comment from '../../../../../common/components/comment';
const CommentTask = (props) => {
return (
<h2> Comment </h2>
<div className="task_comment_task">
<Comment />
</div>
)
}
export default Comment;
export default CommentTask;

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

Loading…
Cancel
Save