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

chromesetting
杨树明 5 years ago
commit 09e386b7ce

@ -21,7 +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 }
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
@ -44,8 +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
# @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}")
@ -99,7 +99,7 @@ class CollegesController < ApplicationController
@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')
@courses = paginate courses
@ -117,7 +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
# @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)
@ -158,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
# 分班情况

@ -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)

@ -1,4 +0,0 @@
class Curriculum < ApplicationRecord
belongs_to :curriculum_direction
has_many :knowledge_points, dependent: :destroy
end

@ -1,4 +0,0 @@
class CurriculumDirection < ApplicationRecord
has_many :curriculums
has_many :knowledge_points
end

@ -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)}

@ -3,8 +3,6 @@ class ItemBank < ApplicationRecord
# 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 :curriculum
belongs_to :curriculum_direction
belongs_to :user
has_one :item_analysis, dependent: :destroy

@ -1,5 +0,0 @@
class KnowledgePoint < ApplicationRecord
belongs_to :curriculum_direction
belongs_to :curriculum
has_many :knowledge_point_containers, dependent: :destroy
end

@ -1,3 +0,0 @@
class KnowledgePointContainer < ApplicationRecord
belongs_to :knowledge_point
end

@ -7,4 +7,4 @@ json.teachers @students do |student|
json.grade student.grade
json.experience student.experience
end
json.student_count @student_count
# json.student_count @student_count

@ -8,4 +8,4 @@ json.teachers @teachers do |teacher|
json.complete_rate teacher['complete_rate']
json.publish_shixun_count teacher['publish_shixun_count']
end
json.teacher_count @teacher_count
# json.teacher_count @teacher_count

@ -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

@ -1,9 +0,0 @@
class CreateCurriculumDirections < ActiveRecord::Migration[5.2]
def change
create_table :curriculum_directions do |t|
t.string :name
t.timestamps
end
end
end

@ -1,10 +0,0 @@
class CreateCurriculums < ActiveRecord::Migration[5.2]
def change
create_table :curriculums do |t|
t.string :name
t.references :curriculum_direction, index: true
t.timestamps
end
end
end

@ -1,11 +0,0 @@
class CreateKnowledgePoints < ActiveRecord::Migration[5.2]
def change
create_table :knowledge_points do |t|
t.string :name
t.references :curriculum_direction, index: true
t.references :curriculum, index: true
t.timestamps
end
end
end

@ -1,12 +0,0 @@
class CreateKnowledgePointContainers < ActiveRecord::Migration[5.2]
def change
create_table :knowledge_point_containers do |t|
t.references :knowledge_point
t.integer :container_id
t.string :container_type
t.timestamps
end
add_index :knowledge_point_containers, [:knowledge_point_id, :container_id, :container_type], name: "container_index", unique: true
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

@ -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",

@ -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={
@ -707,6 +716,7 @@ class App extends Component {
}
}
/>
<Route
path="/problems/:id/edit"
render={
@ -721,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} />)

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;
}

@ -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;
}
}
}
}

@ -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,166 @@
/*
* @Description: quill 编辑器
* @Author: tangjiang
* @Github:
* @Date: 2019-12-18 08:49:30
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 16:58:50
*/
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 _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,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

@ -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-19 19:36:24
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
import { Drawer, Modal } from 'antd';
import { fromStore } from 'educoder';
import { Drawer, Modal, Icon, 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,28 +102,51 @@ 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}/> */}
<span className='flex_strict' style={{ color: '#ddd'}}>{identifier ? language ? maps[language.toLowerCase()] : '' : '学员初始代码文件'}</span>
<span className={_classnames}>{identifier ? '已保存' : ''}</span>
<Badge
className="flex_normal"
style={{ color: '#666'}}
dot={notice}
onClick={handleUpdateNotice}
>
<Icon type="bell" />
</Badge>
<span onClick={handleRestoreCode} className="flex_normal" style={{ display: identifier ? 'inline-block' : 'none'}}>{renderRestore}</span>
<MyIcon className='code-icon' type="iconshezhi" onClick={handleShowDrawer}/>
</div>
<MonacoEditor
@ -131,7 +163,6 @@ function MyMonacoEditor (props, ref) {
<Drawer
className={'setting_drawer'}
placement="right"
closable={false}
onClose={handleDrawerClose}
visible={showDrawer}
>
@ -161,4 +192,4 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyMonacoEditor);
)(CNotificationHOC() (MyMonacoEditor));

@ -1,19 +1,17 @@
.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;
@ -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 09:23:07
*/
import './index.scss';
import React, { useState } from 'react';
import { Collapse, Icon, Input, Form, Button, Modal } from 'antd';
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);
// }
// })
}
// 输入框值改变时
@ -65,54 +73,54 @@ const AddTestDemo = (props) => {
)
// 取消操作
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 +128,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 +158,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 +171,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 +199,4 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form.create()(AddTestDemo));
)(Form.create()(CNotificationHOC()(AddTestDemo)));

@ -4,21 +4,20 @@
* @Github:
* @Date: 2019-11-20 10:35:40
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 11:39:52
* @LastEditTime: 2019-12-19 20:16:32
*/
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;
@ -26,9 +25,9 @@ const { Option } = Select;
const maps = {
language: [
{ 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: '简单', key: '1' },
@ -146,9 +145,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 +181,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 +217,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 +270,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 +308,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', 'overflowY': 'auto' }}
placeholder="请输入描述信息"
onContentChange={handleContentChange}
options={quillConfig}
value={ojForm.description}
/>
</div>
</FormItem>
{/* <FormItem
@ -325,6 +331,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;
@ -63,7 +68,7 @@
top: 43px;
left: -30px;
right: -30px;
padding: 0 30px;
padding: 0 20px;
// background: gold;
background: rgb(249,249,249);
z-index: 1000;
@ -72,5 +77,9 @@
.collapse_area{
margin-bottom: 20px;
.ant-form-item{
margin-bottom: 0px;
}
}
}

@ -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 10:52:36
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
@ -79,7 +79,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 +87,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">

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

@ -8,7 +8,8 @@
.record_detail_header{
height: 65px;
// background:rgba(34,34,34,1);
background: #1E1E1E;
// background: #1E1E1E;
background: rgba(7,15,25,1);
padding:0 30px;
}
@ -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;

@ -0,0 +1,8 @@
.task_comment_task{
background: #fff;
padding: 20px 30px 0;
height: calc(100vh - 177px);
box-sizing: border-box;
overflow-y: auto;
border-bottom: 1px solid rgba(244,244,244,1);
}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 09:49:33
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-05 10:38:49
* @LastEditTime: 2019-12-20 13:55:07
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
@ -73,12 +73,16 @@ const CommitRecord = (props) => {
commitRecord,
// excuteState,
language,
operateType,
commitRecordDetail,
getUserCommitRecord
} = props;
const [pagination, setPagination] = useState(paginationConfig);
const [tableData, setTableData] = useState([]);
let clipboard;
// const [recordDetail, setRecordDetail] = useState({});
const [renderCtx, setRenderCtx] = useState(() => {
return function () {
@ -89,14 +93,14 @@ const CommitRecord = (props) => {
const renderRecordDetail = () => {
const {
id,
error_line,
error_msg,
execute_memory,
execute_time,
input,
output,
// error_line,
// error_msg,
// execute_memory,
// execute_time,
// input,
// output,
status,
expected_output
// expected_output
} = commitRecordDetail;
if (Object.keys(commitRecordDetail).length > 0) {
console.log('当前状态====》》》', status);
@ -143,19 +147,32 @@ const CommitRecord = (props) => {
// 提交详情变化时,显示当前提交信息
useEffect(() => {
// setRecordDetail(commitRecordDetail);
setRenderCtx(() => (renderRecordDetail))
}, [commitRecordDetail]);
if (operateType === 'submit') {
setRenderCtx(() => (renderRecordDetail))
}
}, [commitRecordDetail, operateType]);
// 复制功能
let count = 0;
useEffect(() => {
if (commitRecordDetail.status !== 0) {
const clipboard = new ClipboardJS('.copy_error');
clipboard.on('success', (e) => {
message.success('复制成功');
e.clearSelection();
});
}
}, [commitRecordDetail.status]);
clipboard = new ClipboardJS('.copy_error');
clipboard.on('success', (e) => {
e.clearSelection();
if (count > 0) return;
count++;
message.success('复制成功');
setTimeout(() => {
message.destroy();
}, 300);
});
}, []);
// if (commitRecordDetail.status !== 0) {
// clipboard.on('success', (e) => {
// console.log('成功=====》》》》》');
// message.success('复制成功');
// e.clearSelection();
// });
// }
//
const handleTableChange = (pagination) => {
setPagination(Object.assign({}, pagination));
@ -184,7 +201,8 @@ const mapStateToProps = (state) => {
user_program_identifier,
commitRecordDetail,
commitRecord,
hack
hack,
operateType
} = ojForUserReducer;
const { excuteState } = commonReducer;
return {
@ -192,7 +210,8 @@ const mapStateToProps = (state) => {
commitRecordDetail,
commitRecord, // 提交记录
excuteState, // 代码执行状态
language: hack.language
language: hack.language,
operateType
}
}
const mapDispatchToProps = (dispatch) => ({

@ -1,6 +1,8 @@
.commit_record_area{
// padding: 20px 30px;
padding: 0 30px;
padding: 0 20px;
overflow-y: auto;
height: calc(100vh - 177px);
.record_header{
display: flex;
// justify-content: space-between;

@ -4,11 +4,11 @@
* @Github:
* @Date: 2019-11-23 11:33:41
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-09 19:57:21
* @LastEditTime: 2019-12-19 18:03:22
// */
import './index.scss';
import React, { useState, useEffect, useMemo } from 'react';
import { Tabs, Divider } from 'antd';
import { Divider } from 'antd';
import { connect } from 'react-redux';
import Comment from './comment';
import CommitRecord from './commitRecord';
@ -19,17 +19,10 @@ import actions from '../../../../redux/actions';
const LeftPane = (props) => {
const { hack, userCodeTab, changeUserCodeTab } = props;
const { hack, userCodeTab } = props;
const { pass_count, submit_count } = hack;
const [defaultActiveKey, setDefaultActiveKey] = useState('task');
const [defaultActiveKey, setDefaultActiveKey] = useState('comment');
console.log(pass_count, submit_count);
const tabArrs = [
{ title: '任务描述', key: 'task', content: (<TaskDescription />) },
{ title: '提交记录', key: 'record', content: (<CommitRecord />) },
// { title: '评论', key: 'comment', content: (<Comment />) },
];
const navItem = [
{
title: '任务描述',
@ -38,38 +31,30 @@ const LeftPane = (props) => {
{
title: '提交记录',
key: 'record'
}
},
// {
// title: '评论',
// key: 'comment'
// }
];
const Comp = {
task: (<TaskDescription />),
record: (<CommitRecord />)
record: (<CommitRecord />),
comment: (<Comment />)
};
useEffect(() => {
console.log('====>>>>', userCodeTab);
setDefaultActiveKey(userCodeTab);
}, [userCodeTab])
// 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);
// changeUserCodeTab(key);
// }
const renderComp = useMemo(() => {
return Comp[defaultActiveKey];
}, [defaultActiveKey]);
}, [defaultActiveKey, setDefaultActiveKey]);
const renderNavItem = navItem.map((item) => {
const _classes = item.key === defaultActiveKey ? 'add_editor_item active' : 'add_editor_item';
return (
<li
@ -99,21 +84,6 @@ const LeftPane = (props) => {
return (
<React.Fragment>
{/* <Tabs className={'user_code_tab_area'} activeKey={defaultActiveKey} onChange={handleTabChange}>
{ tabs }
</Tabs>
<div className={'number_area'}>
<div className="number_flex flex_count">
<TextNumber text="通过次数" number={pass_count} position="vertical"/>
<Divider type="vertical" style={{ height: '20px', margin: '10px 20px' }}/>
<TextNumber text="提交次数" number={submit_count} position="vertical"/>
</div>
<div className="number_flex flex_info">
<TextNumber text="message" number={4235} type="icon" onIconClick={handleClickMessage}/>
<TextNumber text="like" number={4235} type="icon" onIconClick={handleClickLike}/>
<TextNumber text="dislike" type="icon" onIconClick={handleClickDisLike}/>
</div>
</div> */}
<ul className={'add_editor_list_area'}>
{ renderNavItem }
</ul>
@ -126,11 +96,6 @@ const LeftPane = (props) => {
<Divider type="vertical" style={{ height: '20px', margin: '10px 20px' }}/>
<TextNumber text="提交次数" number={submit_count} position="vertical"/>
</div>
{/* <div className="number_flex flex_info">
<TextNumber text="message" number={4235} type="icon" onIconClick={handleClickMessage}/>
<TextNumber text="like" number={4235} type="icon" onIconClick={handleClickLike}/>
<TextNumber text="dislike" type="icon" onIconClick={handleClickDisLike}/>
</div> */}
</div>
</React.Fragment>
);

@ -13,8 +13,10 @@
bottom: 0px;
height: 56px;
width: 100%;
box-sizing: border-box;
border-top: 1px solid rgba(244,244,244,1);
// background: pink;
padding: 0 30px;
padding: 0 20px;
// background-color: rgba(250,250,250,1);
background: #fff;
@ -30,7 +32,7 @@
}
.commit_record_area{
padding: 0 30px;
padding: 0 20px;
// height: calc(100vh - 178px);
// overflow-y: auto;
}
@ -38,6 +40,7 @@
.task_desc_area{
height: calc(100vh - 242px);
overflow-y: auto;
padding: 0 0 0 15px;
}
.desc_area_header{
@ -45,7 +48,7 @@
justify-content: space-between;
align-items: center;
height: 64px;
padding: 0 30px;
padding: 0 20px;
.header_flex{
font-size: 14px;
.flex_label{
@ -62,7 +65,7 @@
.student_study_header{
.study_quit{
position: absolute;
right: 30px;
right: 20px;
}
.quit-btn{
cursor: pointer;
@ -79,4 +82,9 @@
margin-right: 5px;
}
}
}
.add_editor_list_area{
box-sizing: border-box;
border-bottom: 1px solid rgba(244,244,244,1);
}

@ -4,14 +4,15 @@
* @Github:
* @Date: 2019-11-27 09:49:30
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-09 19:21:55
* @LastEditTime: 2019-12-20 09:39:35
*/
import '../index.scss';
import React from 'react';
import { Tag } from 'antd';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import QuillEditor from '../../../quillEditor';
import QuillForEditor from '../../../../../common/quillForEditor';
import CONST from '../../../../../constants';
const {tagBackground, diffText} = CONST;
@ -36,22 +37,15 @@ const TaskDescription = (props) => {
</p>
<p className={'header_flex'}>
<span className={'flex_label'}>出题者:</span>
<Link to="/" style={{ color: '#5091FF'}}>{username}</Link>
<Link to="/messages/innov/message_detail" target="_blank" style={{ color: '#5091FF'}}>{username}</Link>
</p>
</div>
<div className="task_desc_area">
<QuillEditor
htmlCtx={description}
<QuillForEditor
readOnly={true}
value={description}
/>
</div>
{/* <QuillEditor
htmlCtx={description}
readOnly={true}
options={[]}
style={{ backgroundColor: 'gold' }}
/> */}
{/* <div dangerouslySetInnerHTML={{__html: description}}></div> */}
</div>
)
}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 14:59:51
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 16:50:40
* @LastEditTime: 2019-12-20 14:01:57
*/
import React, { useState, useEffect } from 'react';
import {connect} from 'react-redux';
@ -20,15 +20,20 @@ const RightPane = (props) => {
submitUserCode,
input,
hack,
notice,
updateCode,
hadCodeUpdate,
editor_code,
updateNotice,
saveUserInputCode,
restoreInitialCode,
saveOpacityType,
saveUserCodeForInterval
} = props;
const [editorCode, setEditorCode] = useState('');
let initFlag = true;
useEffect(() => {
if (editor_code) {
setEditorCode(editor_code);
@ -39,6 +44,7 @@ const RightPane = (props) => {
const handleSubmitForm = () => {
// 提交时, 先调用提交接口,提交成功后,循环调用测评接口
// saveOpacityType('submit');
submitUserCode(identifier, submitInput, 'submit');
// // 提交时,先调用评测接口, 评测通过后才调用保存接口
// updateCode(identifier, submitInput, 'submit');
@ -48,7 +54,12 @@ const RightPane = (props) => {
// 代码块内容变化时
const handleCodeChange = (code) => {
// 保存用户提交的代码块
console.log(code);
setEditorCode(code);
// 第一次回填代码内容时不更新;
if (initFlag) {
initFlag = false;
return;
}
if (!timer) {
timer = setInterval(() => {
clearInterval(timer);
@ -62,6 +73,7 @@ const RightPane = (props) => {
// 代码调试
const handleDebuggerCode = (value) => {
// 调用保存代码块接口,成功后,调用调试接口
// saveOpacityType('debug');
updateCode(identifier, value, 'debug');
}
// 恢复初始代码
@ -69,13 +81,21 @@ const RightPane = (props) => {
restoreInitialCode(identifier, '恢复初始代码成功');
}
// 更新代码
const handleUpdateNotice = () => {
updateNotice && updateNotice();
};
return (
<div className={'right_pane_code_wrap'}>
<MyMonacoEditor
notice={notice}
identifier={identifier}
language={hack.language}
code={editorCode}
hadCodeUpdate={hadCodeUpdate}
onCodeChange={handleCodeChange}
onUpdateNotice={handleUpdateNotice}
onRestoreInitialCode={handleRestoreInitialCode}
/>
<ControlSetting
@ -89,10 +109,19 @@ const RightPane = (props) => {
const mapStateToProps = (state) => {
const {user_program_identifier, hack, userTestInput, editor_code} = state.ojForUserReducer;
const {
user_program_identifier,
hack,
userTestInput,
editor_code,
notice,
hadCodeUpdate
} = state.ojForUserReducer;
// const { language, code } = hack;
return {
hack,
notice,
hadCodeUpdate,
editor_code,
input: userTestInput,
submitInput: hack.input,
@ -111,6 +140,7 @@ const mapDispatchToProps = (dispatch) => ({
saveUserCodeForInterval: (identifier, code) => dispatch(actions.saveUserCodeForInterval(identifier, code)),
// 恢复初始代码
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
// saveOpacityType: (type) => dispatch(actions.saveOpacityType(type))
});
export default connect(

@ -14,4 +14,29 @@
color: #fff;
left: 13px;
user-select: none;
}
.jupyter_float_button {
background-image: url(./images/float_switch.jpg);
height: 112px;
width: 38px;
position: absolute;
right: 0px;
top: 32%;
cursor: pointer;
left:auto;
z-index: 99999999;
}
.jupyter_float_button .text {
position: relative;
writing-mode: vertical-rl;
top: 36px;
color: #fff;
left: 13px;
user-select: none;
}
.newjupyter_float_button{
right: 330px;
}

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import '../VNC.css'
const $ = window.$;
class FloatButton extends Component {
componentDidMount() {

@ -1218,7 +1218,7 @@ submittojoinclass=(value)=>{
}
{
this.props.Headertop && this.props.Headertop.laboratory_user &&
<li><a href="/cooperative">后台管理</a></li>
<li><a href={this.props.Headertop.laboratory_admin_url}>后台管理</a></li>
}
<li><a href={`/account/profile`}>账号管理</a></li>

@ -58,15 +58,6 @@ class TPMBanner extends Component {
}
}
// star_info:[0, 0, 0, 0, 0, 0],
// star_infos:[0, 0, 0, 0, 0, 0],
// shixunsDetails:{},
// shixunId: undefined,
// componentWillReceiveProps(newProps, newContext){
// this.setState({
// shixunsDetails: newProps.shixunsDetails
// });
// }
IEVersion = () => {
var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
@ -118,31 +109,52 @@ class TPMBanner extends Component {
if (prevProps != this.props) {
let shixunopenprocess=window.localStorage.shixunopenprocess;
let openopenpublictype=window.localStorage.openopenpublictype;
if(this.props.shixunsDetails&&this.props.shixunsDetails.shixun_status === 0 && this.props.identity < 5){
if(shixunopenprocess===undefined||shixunopenprocess===false){
this.setState({
openknow:true
})
}else{
this.setState({
openknow:false
})
if(this.props.status===0&&this.props.openknows===false){
if(this.props.shixunsDetails&&this.props.shixunsDetails.shixun_status === 0 && this.props.identity < 5){
if(shixunopenprocess===undefined||shixunopenprocess===false){
this.setState({
openknow:true
})
}else{
this.setState({
openknow:false
})
}
}
}else{
this.setState({
openknow:false
})
}
if(this.props.shixunsDetails&&this.props.shixunsDetails.shixun_status === 2 && this.props.shixunsDetails&&this.props.shixunsDetails.public===0 && this.props.identity < 5){
if(openopenpublictype===undefined||openopenpublictype===false){
this.setState({
openshowpublictype:true
})
}else{
this.setState({
openshowpublictype:false
})
if(this.props.public===0&&this.props.status>1&&this.props.openknows===false){
if(this.props.shixunsDetails&&this.props.shixunsDetails.shixun_status === 2 && this.props.shixunsDetails&&this.props.shixunsDetails.public===0 && this.props.identity < 5){
if(openopenpublictype===undefined||openopenpublictype===false){
this.setState({
openshowpublictype:true
})
}else{
this.setState({
openshowpublictype:false
})
}
}
}else{
this.setState({
openshowpublictype:false
})
}
}
}
componentDidMount() {
let thiisie = this.IEVersion();
@ -155,6 +167,7 @@ class TPMBanner extends Component {
isIE: false
})
}
}
/*
@ -784,7 +797,8 @@ class TPMBanner extends Component {
};
//
// console.log(this.props.shixunsDetails&&this.props.shixunsDetails.is_jupyter)
// console.log(this.props)
// console.log(this.state)
return (
shixunsDetails === undefined ? "" :
@ -796,6 +810,9 @@ class TPMBanner extends Component {
height: 180px !important;
padding-top:35px !important;
}
.ant-popover{
z-index:1000 !important;
}
`
}
</style>
@ -851,8 +868,8 @@ class TPMBanner extends Component {
</li>
</ul>
{
this.props.is_jupyter===true?"":
{/*{*/}
{/* this.props.is_jupyter===true?"":*/}
<Popover placement="right" content={
<div style={{"width": "530px"}} >
@ -917,7 +934,7 @@ class TPMBanner extends Component {
</div>
</Popover>
}
{/*// }*/}
{
@ -1001,8 +1018,8 @@ class TPMBanner extends Component {
<Popover
content={
<pre className={"bannerpd201"}>
<div>您编辑完成后可以马上使用到自</div>
<div className={"wechatcenter mt10"}>己的课堂和实训课程</div>
<div>点击发布后可以马上应用到自</div>
<div className={"wechatcenter mt10"}>己的课堂和课程</div>
<div className={"wechatcenter mt15"}><Button type="primary" onClick={this.openknow} >我知道了</Button></div>
</pre>
}

@ -461,7 +461,7 @@ class TPMDataset extends Component {
const uploadProps = {
width: 600,
fileList,
multiple: false,
multiple: true,
//multiple 是否支持多选 查重的时候不能多选 不然弹许多框出来
// https://github.com/ant-design/ant-design/issues/15505
// showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。

@ -207,6 +207,7 @@ class TPMIndex extends Component {
propaedeutics:response.data.propaedeutics,
status: response.data.shixun_status,
secret_repository: response.data.secret_repository,
public:response.data.public,
is_jupyter:response.data.is_jupyter=== undefined||response.data.is_jupyter===null?false:response.data.is_jupyter,
});
@ -295,10 +296,6 @@ class TPMIndex extends Component {
// this.getnavdatas()
}
// componentDidUpdate=()=>{
// this.getnavdatas()
// }
setLoadingContent = (isLoadingContent) => {
this.setState({ loadingContent: isLoadingContent })
}
@ -307,9 +304,6 @@ class TPMIndex extends Component {
getnavdatas=()=>{
let selectedKeys;
const {location} = this.props;
console.log(location.pathname)
if(location.pathname.indexOf('/challenges')!=-1){
selectedKeys="1"
}else if(location.pathname.indexOf('/propaedeutics')!=-1){
@ -385,6 +379,9 @@ class TPMIndex extends Component {
margin:0 40px 0 0;
padding:0px;
}
.ant-popover{
z-index:1000 !important;
}
`
}
</style>

@ -215,9 +215,9 @@ export default class Shixuninformation extends Component {
multi_webssh: false
})
} else {
this.setState({
multi_webssh: true
})
// this.setState({
// multi_webssh: true
// })
}
this.setState({
opensshRadio: e.target.value

@ -50,6 +50,14 @@ class Shixuninformation extends Component {
}
componentDidMount() {
let query=this.props.location.search
const types = query.split('?edit=')
if(types[1]==="1"){
let anchorElement = document.getElementById("newcourseContentMD");
if(anchorElement){
this.scrollToAnchor("newcourseContentMD");
}
}
}
@ -113,6 +121,7 @@ class Shixuninformation extends Component {
selectright: this.props.data && this.props.data.shixun.choice_small_type,
})
this.contentMdRef.current.setValue(this.props.data && this.props.data.shixun.description);
}
}
}
@ -807,16 +816,20 @@ class Shixuninformation extends Component {
)}
</Form.Item>
<span id={"newcourseContentMD"}></span>
<Form.Item
label="简介"
style={{"borderBottom": 'none', 'marginBottom': '0px'}}
className="chooseDes pr"
>
<TPMMDEditor ref={this.contentMdRef} placeholder="请输入简介" mdID={'courseContentMD'}
refreshTimeout={1500}
className="courseMessageMD"
// initValue={this.state.description === null ? "" : this.state.description}
></TPMMDEditor>
</Form.Item>

@ -302,11 +302,11 @@ export default class TPManswer extends Component {
className="color-grey-6 fr font-15 mt3">返回</Link>
{prev_challenge === undefined ? "" :
<Link to={prev_challenge} className="fr color-blue mr15 mt4">上一关</Link>
<a href={prev_challenge} className="fr color-blue mr15 mt4">上一关</a>
}
{next_challenge === undefined ? "" :
<Link to={next_challenge} className="fr color-blue mr15 mt4">下一关</Link>
<a href={next_challenge} className="fr color-blue mr15 mt4">下一关</a>
}
<a href={practice_url === undefined ? "" : practice_url}

@ -265,11 +265,11 @@ export default class TPManswer extends Component {
className="color-grey-6 fr font-15 mt3">返回</Link>
{prev_challenge === undefined ? "" :
<Link to={prev_challenge} className="fr color-blue mr15 mt4">上一关</Link>
<a href={prev_challenge} className="fr color-blue mr15 mt4">上一关</a>
}
{next_challenge === undefined ? "" :
<Link to={next_challenge} className="fr color-blue mr15 mt4">下一关</Link>
<a href={next_challenge} className="fr color-blue mr15 mt4">下一关</a>
}
<a href={practice_url === undefined ? "" : practice_url}

@ -429,10 +429,10 @@ export default class TPMchallengesnew extends Component {
<Link to={go_back_url === undefined ? "" : go_back_url}
className="color-grey-6 fr font-15 mt3">返回</Link>
{ next_challenge===undefined?"":
<Link to={next_challenge}className="fr color-blue mr15 mt4">下一关</Link>
<a href={next_challenge}className="fr color-blue mr15 mt4">下一关</a>
}
{ prev_challenge===undefined?"":
<Link to={prev_challenge} className="fr color-blue mr15 mt4">上一关</Link>
<a href={prev_challenge} className="fr color-blue mr15 mt4">上一关</a>
}

@ -824,11 +824,11 @@ export default class TPMevaluation extends Component {
className="color-grey-6 fr font-15 mt3">返回</Link>
{prev_challenge === undefined ? "" :
<Link to={prev_challenge} className="fr color-blue mr15 mt4">上一关</Link>
<a href={prev_challenge} className="fr color-blue mr15 mt4">上一关</a>
}
{next_challenge === undefined ? "" :
<Link to={next_challenge} className="fr color-blue mr15 mt4">下一关</Link>
<a href={next_challenge} className="fr color-blue mr15 mt4">下一关</a>
}
<a href={practice_url === undefined ? "" : practice_url}

@ -936,11 +936,11 @@ export default class TPMquestion extends Component {
<Link to={go_back_url === undefined ? "" : go_back_url}
className="color-grey-6 fr font-15 mt3">返回</Link>
{ prev_challenge===undefined?"":
<Link to={prev_challenge} className="fr color-blue mr15 mt4">上一关</Link>
<a href={prev_challenge} className="fr color-blue mr15 mt4">上一关</a>
}
{ next_challenge===undefined?"":
<Link to={next_challenge}className="fr color-blue mr15 mt4">下一关</Link>
<a href={next_challenge}className="fr color-blue mr15 mt4">下一关</a>
}
<a href={practice_url === undefined ? "" : practice_url}

@ -63,6 +63,8 @@ class TPMRightSection extends Component {
if(TPMRightSectionData&&TPMRightSectionData.complete_count!=null){
Progresssum=(parseInt(TPMRightSectionData&&TPMRightSectionData.complete_count) / parseInt(TPMRightSectionData&&TPMRightSectionData.challenge_count))*100;
}
return (
<div>
{
@ -97,11 +99,11 @@ class TPMRightSection extends Component {
</div>
</div>
{TPMRightSectionData&&TPMRightSectionData.complete_count!=null?<div className="edu-back-white padd252020px relative borderbottomf4">
{this.props&&this.props.status>1?<div className="edu-back-white padd252020px relative borderbottomf4">
<div className="font-16 mb5">
<span><i className={"iconfont icon-jilu1 audit_situationactive font-14"}></i> </span>
<span className={"sortinxdirection space-between fr"}>
<span className="color888hezuo font-12">已完成 {TPMRightSectionData&&TPMRightSectionData.complete_count} / {TPMRightSectionData&&TPMRightSectionData.challenge_count} </span>
<span className="color888hezuo font-12">已完成 {TPMRightSectionData&&TPMRightSectionData.complete_count===null?0:TPMRightSectionData&&TPMRightSectionData.complete_count} / {TPMRightSectionData&&TPMRightSectionData.challenge_count} </span>
</span>
</div>

@ -119,9 +119,14 @@
.padd252020px{
padding: 25px 20px 15px;
}
.rightjinengs{
height: 35px;
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.borderbottomf4{

@ -9,14 +9,34 @@
import './index.scss';
import React, { useEffect, useState } from 'react';
import SplitPane from 'react-split-pane';
import { Button, Modal } from 'antd';
import { Button, Modal,Drawer ,Pagination,Empty,Tooltip,Icon,message} from 'antd';
import {
connect
} from 'react-redux';
import FloatButton from '../../page/component/FloatButton';
import UserInfo from '../../developer/components/userInfo';
import actions from '../../../redux/actions';
import LeftPane from './leftPane';
import RightPane from './rightPane';
import MyIcon from "../../../common/components/MyIcon";
function jsCopy(s) {
var copyEle = document.getElementById(s);
const range = document.createRange(); // 创造range
window.getSelection().removeAllRanges(); //清除页面中已有的selection
range.selectNode(copyEle); // 选中需要复制的节点
window.getSelection().addRange(range); // 执行选中元素
const copyStatus = document.execCommand("Copy"); // 执行copy操作
// 对成功与否定进行提示
copyStatuss(copyStatus)
}
function copyStatuss(copyStatus){
if (copyStatus) {
message.success('复制成功');
} else {
message.error('复制失败');
}
}
function JupyterTPI (props) {
// 获取 identifier 值
@ -39,14 +59,22 @@ function JupyterTPI (props) {
changeLoadingState,
changeGetJupyterUrlState,
jupyter_identifier,
changeCurrentPage
changeCurrentPage,
changeshowDrawer,
drawervisible,
} = props;
const emptyCtx = (
<div className="jupyter_empty">
<Empty />
</div>
);
const {identifier} = params;
const [userInfo, setUserInfo] = useState({});
const [jupyterInfo, setJupyterInfo] = useState({});
const [updateTip, setUpdateTip] = useState(true);
const [myIdentifier, setMyIdentifier] = useState('');
const [renderCtx, setRenderCtx] = useState(() => (emptyCtx));
useEffect(() => {
/* jupyter TPI
* 获取 用户信息,
@ -107,6 +135,28 @@ function JupyterTPI (props) {
})
}
// 重置环境
const handleEnvironmentTpi = () => {
Modal.confirm({
title: '重置环境',
content: (
<p style={{ lineHeight: '24px' }}>
你在本文件中修改的内容将丢失,<br />
是否确定重置环境
</p>
),
okText: '确定',
cancelText: '取消',
onOk () {
// console.log('调用重置代码....', myIdentifier);
// if (myIdentifier) {
// syncJupyterCode(myIdentifier, '重置成功');
// }
}
})
}
// 退出实训
const handleClickQuitTpi = () => {
// console.log(jupyterInfo);
@ -138,6 +188,52 @@ function JupyterTPI (props) {
getJupyterTpiDataSet(jupyter_identifier);
}
const swtichFirstDrawer = () => {
changeshowDrawer(!drawervisible)
}
const firstDrawerWidth = ()=>{
return 260
};
// 分页处理
const handleChangePage = (page) => {
// console.log(page, pageSize);
handlePageChange(page);
}
// const listCtx = ;
useEffect(() => {
if (dataSets.length > 0) {
console.log('数据集的个数: ', dataSets.length);
const oList = dataSets.map((item, i) => {
return (
<li className="jupyter_item" key={`key_${i}`}>
<Tooltip
placement="right"
// title={item.file_path}
mouseLeaveDelay={0.3}
>
<Icon type="file-text" className="jupyter_icon"/>
<span className="jupyter_name ml10">{item.title}</span>
<a className={"fr color-blue"}
onClick={() => {
jsCopy("file_path"+i)
}}>复制地址</a>
<input id={"file_path"+i} className={"file_path_input"} value={item.file_path}/>
</Tooltip>
</li>
);
});
const oUl = (
<ul className="jupyter_data_list">
{ oList }
</ul>
);
setRenderCtx(oUl);
}
}, [props]);
return (
<div className="jupyter_area">
<div className="jupyter_header">
@ -151,9 +247,17 @@ function JupyterTPI (props) {
<Button
className="btn_common"
type="link"
icon="sync"
icon="history"
onClick={handleClickResetTpi}
>重置实训</Button>
>重置实训</Button>
<Button
className="btn_common"
type="link"
icon="sync"
onClick={handleEnvironmentTpi}
>重置环境</Button>
<Button
className="btn_common"
type="link"
@ -162,16 +266,17 @@ function JupyterTPI (props) {
>退出实训</Button>
</p>
</div>
<div className="jupyter_ctx">
<SplitPane split="vertical" minSize={350} maxSize={-350} defaultSize="30%">
<div className={'split-pane-left'}>
<LeftPane
dataSets={dataSets}
total={total}
pagination={pagination}
onPageChange={handlePageChange}
/>
</div>
<SplitPane split="vertical" minSize={350} maxSize={-350} defaultSize="100%">
{/*<div className={'split-pane-left'}>*/}
{/* <LeftPane*/}
{/* dataSets={dataSets}*/}
{/* total={total}*/}
{/* pagination={pagination}*/}
{/* onPageChange={handlePageChange}*/}
{/* />*/}
{/*</div>*/}
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane
identifier={myIdentifier}
@ -181,9 +286,38 @@ function JupyterTPI (props) {
onReloadUrl={handleOnReloadUrl}
onSave={handleOnSave}
/>
<div />
<FloatButton onClick={swtichFirstDrawer} className={drawervisible===false?"jupyter_float_button":"jupyter_float_button newjupyter_float_button"}>{"数据集"}</FloatButton>
</SplitPane>
</SplitPane>
<Drawer
placement={"right"}
closable={false}
mask={false}
// onClose={this.onClose}
visible={drawervisible}
className={"RightPaneDrawer"}
>
<p className={"RightPaneDrawertop"}></p>
<div className="jupyter_data_sets_area newjupyter_data_sets_area">
<h2 className="jupyter_h2_title">
{/*<MyIcon type="iconwenti" className="jupyter_data_icon"/>*/}
<i className={"iconfont icon-base"}></i>
{/* <span className="iconfont icon-java jupyter_data_icon"></span>数据集 */}
</h2>
{ renderCtx }
<div className='jupyter_pagination'>
{total<20?"":<Pagination
simple
current={pagination.page}
pageSize={pagination.limit}
total={total}
onChange={handleChangePage}
/>}
</div>
</div>
</Drawer>
</div>
</div>
);
@ -199,7 +333,7 @@ const mapStateToProps = (state) => {
jupyter_pagination,
jupyter_identifier
} = state.jupyterReducer;
const { loading } = state.commonReducer;
const { loading ,drawervisible} = state.commonReducer;
return {
loading,
jupyter_info,
@ -208,7 +342,8 @@ const mapStateToProps = (state) => {
jupyter_tpi_url_state,
total: jupyter_data_set_count,
pagination: jupyter_pagination,
jupyter_identifier
jupyter_identifier,
drawervisible,
};
}
@ -221,7 +356,9 @@ const mapDispatchToProps = (dispatch) => ({
getJupyterTpiUrl: (identifier) => dispatch(actions.getJupyterTpiUrl(identifier)),
saveJupyterTpi: () => dispatch(actions.saveJupyterTpi()),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag)),
changeCurrentPage: (current) => dispatch(actions.changeCurrentPage(current))
changeCurrentPage: (current) => dispatch(actions.changeCurrentPage(current)),
//展开Drawer
changeshowDrawer: (type) => dispatch(actions.changeshowDrawer(type))
});
export default connect(

@ -9,7 +9,7 @@
-webkit-background-clip: padding;
background-clip: padding-box;
}
.Resizer:hover {
-webkit-transition: all 2s ease;
transition: all 2s ease;
@ -56,6 +56,7 @@
line-height: 60px;
background-color: #070F1A;
padding-left: 30px;
z-index:999999;
.jupyter_title{
display: flex;
flex-direction: column;
@ -102,4 +103,59 @@
font-size: 14px;
}
}
}
}
.RightPaneDrawer{
.RightPaneDrawertop{
width:330px;
height:29px;
background:rgba(17,28,36,1);
}
.ant-drawer-content-wrapper{
width:330px !important;
box-shadow: -2px 0 8px #070F1A !important;
}
.ant-drawer-body{
padding: 0px;
}
.ant-drawer-wrapper-body{
padding-top: 60px;
background: #070F1A;
overflow: hidden !important;
}
.ant-pagination{
color:#fff !important;
}
}
.newjupyter_data_sets_area{
background:#070F1A !important;
.jupyter_h2_title {
height:49px;
line-height: 49px;
background: #070F1A !important;
border-bottom: 1px solid #17212F !important;
color:#FFFFFF !important;
border-top: 1px solid #17212F !important;
}
.iconfont{
color:#28b887!important;
font-size: 30px !important;
margin-right: 20px;
}
.jupyter_pagination{
border-top: 1px solid #070F1A !important;
}
.jupyter_name{
color:#FFFFFF !important;
}
.file_path_input{
position: absolute;
right: -50%;
}
}

@ -2,15 +2,15 @@
height: 100%;
background: #fff;
.jupyter_h2_title{
height: 44px;
line-height: 44px;
//height: 44px;
//line-height: 44px;
// background-color: #EEEEEE;
background: #fff;
padding: 0 30px;
padding: 0 20px;
font-size: 16px;
// box-size: border-box;
box-sizing: border-box;
border-bottom: 1px solid rgba(238,238,238,1);
//border-bottom: 1px solid rgba(238,238,238,1);
.jupyter_data_icon{
// color: #7286ff;
color: #1890ff;
@ -24,14 +24,15 @@
.jupyter_data_list,
.jupyter_empty{
height: calc(100vh - 160px);
//height: calc(100vh - 160px);
min-height: 350px;
overflow-y: auto;
}
.jupyter_data_list{
.jupyter_item{
line-height:45px;
border-bottom: 1px solid rgba(238,238,238, 1);
//border-bottom: 1px solid rgba(238,238,238, 1);
padding: 0 30px 0 60px;
overflow: hidden;
text-overflow:ellipsis;

@ -68,7 +68,7 @@
align-items: center;
height: 56px;
justify-content: flex-end;
padding-right: 30px;
padding-right: 20px;
}
}
}

@ -10,8 +10,6 @@ import { Modal, Spin, Tooltip ,message,Icon,Button,Divider} from 'antd';
import axios from 'axios';
import 'antd/lib/pagination/style/index.css';
import '../shixunchildCss/Challenges.css';
import AccountProfile from"../../../user/AccountProfile";
@ -34,7 +32,8 @@ class Challenges extends Component {
operationstrue:false,
isSpin:false,
boxoffsetHeigh:0,
opentitletype:true
opentitletype:true,
isopentitletype:"Less",
}
}
@ -52,6 +51,13 @@ class Challenges extends Component {
ChallengesDataList: response.data,
sumidtype: false,
});
if(response.data.description=== ""||response.data.description===null||response.data.description===undefined){
this.setState({
isopentitletype:"Less",
})
}
this.getjianjiesize()
}
}
}).catch((error) => {
@ -59,29 +65,54 @@ class Challenges extends Component {
});
}
getjianjiesize=()=>{
let {ChallengesDataList}=this.state;
let boxoffsetHeigh;
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined){
}else{
if(this.state.isopentitletype==="greater"){
}else{
if(boxoffsetHeigh>=300){
this.setState({
opentitletype:true,
isopentitletype:"greater",
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
isopentitletype:"Less",
boxoffsetHeigh:boxoffsetHeigh
})
}
}
}
}
}
componentDidMount() {
this.ChallengesList()
if(this.state.isopentitletype==="greater"){
}else {
this.ChallengesList()
}
}
componentDidUpdate = (prevProps,prevState) => {
//防止陷入无限循环
if(prevState.ChallengesDataList!=this.state.ChallengesDataList){
let boxoffsetHeigh;
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(boxoffsetHeigh<260){
this.setState({
opentitletype:false,
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
boxoffsetHeigh:boxoffsetHeigh
})
}
if(this.state.isopentitletype==="greater"){
}else{
this.getjianjiesize()
}
}
@ -322,7 +353,7 @@ class Challenges extends Component {
opentitle=()=>{
this.setState({
opentitletype:!this.state.opentitletype
opentitletype:!this.state.opentitletype,
})
}
@ -334,6 +365,7 @@ class Challenges extends Component {
}
let id = this.props.match.params.shixunId;
const antIcon = <Icon type="loading" style={{ fontSize: 24 }} spin />;
return (
<React.Fragment>
{AccountProfiletype===true?<AccountProfile
@ -395,24 +427,15 @@ class Challenges extends Component {
<span className="font-16 fl">简介</span>
{this.props.identity < 5 && ChallengesDataList&&ChallengesDataList.shixun_status < 3 ?
<Link to={"/shixuns/" + id + "/settings"} className="fr color-blue font-14">
<a href={"/shixuns/" + id + "/settings?edit=1"} className="fr color-blue font-14">
{/*<img src={getImageUrl("images/educoder/icon/edit.svg")} className="fl mt3 ml2" />*/}
编辑
</Link>:""}
</a>:""}
{this.props.user && this.props.user.main_site === true ?
this.props.identity < 5?<a className="fr font-14 color-blue mr20" href="/forums/2943"
target="_blank">实训制作指南</a> : "":""}
</div>
{this.state.opentitletype===true?<style>
{
`
#shixunchallengesid{
max-height: 260px;
overflow: hidden;
}
`
}
</style>:""}
<div>
<div className={"pd20"} id={"shixunchallengesid"}>
<style>
@ -424,17 +447,59 @@ class Challenges extends Component {
`
}
</style>
<style>
{
`
.markdown-body img{
min-height: 200px;
}
`
}
</style>
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?<style>
{
`
#shixunchallengesid{
max-height: 300px;
overflow: hidden;
}
`
}
</style>:""}
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?"":this.state.isopentitletype==="greater"&&this.state.opentitletype===true?
<style>
{
`
#shixunchallengesid{
max-height:260px;
overflow: hidden;
}
`
}
</style>:""}
<div className="justify break_full_word new_li "
id="challenge_editorMd_description">
<p id="ReactMarkdown" style={{overflow:'hidden'}}>
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?
<div className="edu-tab-con-box clearfix edu-txt-center">
{this.props.identity < 5?<img className="newedu-nodata-img mb20"
src={getImageUrl("images/educoder/shixunnodata.png")} />:<img className="edu-nodata-img mb20"
src={getImageUrl("images/educoder/nodata.png")} />}
<p className="edu-nodata-p mb80">暂时还没有相关数据哦</p>
</div>
:<p id="ReactMarkdown" style={{overflow:'hidden'}}>
{ChallengesDataList === undefined ? "" :ChallengesDataList&&ChallengesDataList.description===null?"":
<div className={"markdown-body"} dangerouslySetInnerHTML={{__html: markdownToHTML(ChallengesDataList.description).replace(/▁/g,"▁▁▁")}}></div>
}
</p>
</p>}
</div>
</div>
{this.state.opentitletype===true?<Divider dashed={true} onClick={()=>this.opentitle()} className={"pointer Breadcrumbfont color-grey-9 "}>
{this.state.isopentitletype==="Less"?"":this.state.opentitletype===true?<Divider dashed={true} onClick={()=>this.opentitle()} className={"pointer Breadcrumbfont color-grey-9 "}>
<a className={"font-14 color-grey-9"}>阅读全文 <i className={"iconfont icon-jiantou9 font-14"}></i></a>
</Divider>:<Divider dashed={true} onClick={()=>this.opentitle()} className={"pointer Breadcrumbfont color-grey-9 "}>
<a className={"font-14 color-grey-9"}>收起全文 <i className={"iconfont icon-changyongtubiao-xianxingdaochu-zhuanqu- font-14"}></i></a>

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { Link } from "react-router-dom";
import { markdownToHTML, configShareForCustom} from 'educoder'
import { markdownToHTML, configShareForCustom,getImageUrl} from 'educoder'
import { Divider, Tooltip } from 'antd';
import LoadingSpin from '../../../../common/LoadingSpin';
import 'antd/lib/pagination/style/index.css';
@ -24,6 +24,7 @@ class Challengesjupyter extends Component {
loading:false,
boxoffsetHeigh:0,
opentitletype:true,
isopentitletype:"Less",
enlarge:false,
}
}
@ -42,31 +43,59 @@ class Challengesjupyter extends Component {
ChallengesDataList: response.data,
sumidtype: false,
});
if(response.data.description=== ""||response.data.description===null||response.data.description===undefined){
this.setState({
isopentitletype:"Less",
})
}
this.getjianjiesize()
}
}
}).catch((error) => {
//console.log(error)
});
}
getjianjiesize=()=>{
let {ChallengesDataList}=this.state;
let boxoffsetHeigh;
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined){
}else{
if(this.state.isopentitletype==="greater"){
}else{
if(boxoffsetHeigh>=300){
this.setState({
opentitletype:true,
isopentitletype:"greater",
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
isopentitletype:"Less",
boxoffsetHeigh:boxoffsetHeigh
})
}
}
}
}
}
componentDidUpdate = (prevProps,prevState) => {
//防止陷入无限循环
if(prevState.ChallengesDataList!=this.state.ChallengesDataList){
let boxoffsetHeigh;
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(boxoffsetHeigh<260){
this.setState({
opentitletype:false,
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
boxoffsetHeigh:boxoffsetHeigh
})
}
if(this.state.isopentitletype==="greater"){
}else{
this.getjianjiesize()
}
}
@ -76,9 +105,9 @@ class Challengesjupyter extends Component {
setTimeout(this.ChallengesList(), 1000);
let id = this.props.match.params.shixunId;
let ChallengesURL = `/jupyters/get_info_with_tpm.json`;
let datas={
identifier:id,
}
let datas={
identifier:id,
}
axios.get(ChallengesURL, {params: datas}).then((response) => {
if (response.data.status === 403||response.data.status === 401||response.data.status === 500) {
setTimeout(() => {
@ -86,26 +115,29 @@ class Challengesjupyter extends Component {
booljupyterurls:true,
})
}, 600)
}else{
if(response.data.status===0){
}else{
if(response.data.status===0){
setTimeout(() => {
this.setState({
jupyter_url:response.data.url,
jupyter_port:response.data.port,
booljupyterurls:true,
})
}, 800)
setTimeout(() => {
}else{
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
var url=response.data.url;
var url2=url.replace("http://","https://");
this.setState({
jupyter_url:url2,
jupyter_port:response.data.port,
booljupyterurls:true,
})
}, 800)
}else{
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}
}
}
}).catch((error) => {
@ -116,19 +148,22 @@ class Challengesjupyter extends Component {
}, 600)
});
setTimeout(this.getjianjiesize(), 1000);
let self = this; //为了避免作用域及缓存
window.receiveMessageFromIndex = function ( event ) {
if(event!=undefined){
console.log("触发了jupytermessage");
console.log("触发了jupytermessage");
// self.modifyjupyter();
window.addEventListener('message', (e) => {
console.log("触发了jupytermessage");
console.log(e);
if(e){
if(e.data){
if(e.data==="jupytermessage"){
that.modifyjupyter();
}
}
}
}
//监听message事件
window.addEventListener("jupytermessage", receiveMessageFromIndex, false);
});
}
updatamakedowns = () => {
@ -182,13 +217,13 @@ class Challengesjupyter extends Component {
modifyjupyter=()=>{
let id=this.props.match.params.shixunId;
var jupyter_port="";
try{
jupyter_port= parseInt(this.state.jupyter_port);
}catch (e) {
jupyter_port=this.state.jupyter_port;
var jupyter_port="";
try{
jupyter_port= parseInt(this.state.jupyter_port);
}catch (e) {
jupyter_port=this.state.jupyter_port;
}
}
const url=`/jupyters/save_with_tpm.json`;
const data={
identifier:id,
@ -197,8 +232,8 @@ class Challengesjupyter extends Component {
axios.get(url, {params: data})
.then((result) => {
if (result.data.status === 0) {
this.props.showNotification(`应用成功`);
console.log("触发了jupytermessage调用了应用成功");
// this.props.showNotification(`应用成功`);
console.log("应用成功");
}
}).catch((error) => {
})
@ -209,14 +244,12 @@ class Challengesjupyter extends Component {
opentitletype:!this.state.opentitletype
})
}
onclki=(bool)=>{
this.setState({
enlarge:bool
})
}
render() {
let{ChallengesDataList,booljupyterurls,enlarge}=this.state;
let id = this.props.match.params.shixunId;
@ -241,29 +274,64 @@ class Challengesjupyter extends Component {
<div className={"shixunjianjie"}>
<span className="font-16 fl">简介</span>
<Tooltip placement="bottom" title={"编辑"}>
<Link style={{ display: this.props.identity < 5 && ChallengesDataList&&ChallengesDataList.shixun_status < 3 ? "block" : 'none' }}
to={"/shixuns/" + id + "/settings?edit=1"} className="fr color-blue font-14">
<a style={{ display: this.props.identity < 5 && ChallengesDataList&&ChallengesDataList.shixun_status < 3 ? "block" : 'none' }}
href={"/shixuns/" + id + "/settings?edit=1"} className="fr color-blue font-14">
编辑
</Link>
</a>
</Tooltip>
</div>
{this.state.opentitletype===true?<style>
{
`
<div>
<style>
{
`
.markdown-body img{
min-height: 200px;
}
`
}
</style>
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?<style>
{
`
#shixunchallengesid{
max-height: 260px;
max-height: 300px;
overflow: hidden;
}
`
}
</style>:""}
<div>
}
</style>:""}
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?"":this.state.isopentitletype==="greater"&&this.state.opentitletype===true?
<style>
{
`
#shixunchallengesid{
max-height:260px;
overflow: hidden;
}
`
}
</style>:""}
<div className={"pd20"} id={"shixunchallengesid"}>
<p id="ReactMarkdown" style={{overflow:'hidden'}}>
{ChallengesDataList === undefined ? "" :ChallengesDataList&&ChallengesDataList.description===null?"":
<div className={"markdown-body"} dangerouslySetInnerHTML={{__html: markdownToHTML(ChallengesDataList.description).replace(/▁/g,"▁▁▁")}}></div>
}
{/*{ChallengesDataList === undefined ? "" :ChallengesDataList&&ChallengesDataList.description===null?"":*/}
{/* <div className={"markdown-body"} dangerouslySetInnerHTML={{__html: markdownToHTML(ChallengesDataList.description).replace(/▁/g,"▁▁▁")}}></div>*/}
{/*}*/}
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?
<div className="edu-tab-con-box clearfix edu-txt-center">
{this.props.identity < 5?<img className="newedu-nodata-img mb20"
src={getImageUrl("images/educoder/shixunnodata.png")} />:<img className="edu-nodata-img mb20"
src={getImageUrl("images/educoder/nodata.png")} />}
<p className="edu-nodata-p mb80">暂时还没有相关数据哦</p>
</div>
:<p id="ReactMarkdown" style={{overflow:'hidden'}}>
{ChallengesDataList === undefined ? "" :ChallengesDataList&&ChallengesDataList.description===null?"":
<div className={"markdown-body"} dangerouslySetInnerHTML={{__html: markdownToHTML(ChallengesDataList.description).replace(/▁/g,"▁▁▁")}}></div>
}
</p>}
</p>
{
booljupyterurls===true?
@ -281,7 +349,7 @@ class Challengesjupyter extends Component {
}
</div>
{this.state.opentitletype===true?<Divider dashed={true} onClick={()=>this.opentitle()} className={"pointer Breadcrumbfont color-grey-9 "}>
{this.state.isopentitletype==="Less"?"":this.state.opentitletype===true?<Divider dashed={true} onClick={()=>this.opentitle()} className={"pointer Breadcrumbfont color-grey-9 "}>
<a className={"font-14 color-grey-9"}>阅读全文 <i className={"iconfont icon-jiantou9 font-14"}></i></a>
</Divider>:<Divider dashed={true} onClick={()=>this.opentitle()} className={"pointer Breadcrumbfont color-grey-9 "}>
<a className={"font-14 color-grey-9"}>收起全文 <i className={"iconfont icon-changyongtubiao-xianxingdaochu-zhuanqu- font-14"}></i></a>
@ -311,9 +379,8 @@ class Challengesjupyter extends Component {
display: flex;
flex-direction:row-reverse;
}
;
}
}
`
}
</style>
@ -322,35 +389,35 @@ class Challengesjupyter extends Component {
""
:
(
admin===true||business===true||mysidentity===true?
<div style={{
height: '63px',
}} className={enlarge?"shixunjianjiecballenges edu-back-white intermediatecenter fangdaone":"shixunjianjiecballenges edu-back-white mt20"}>
<div className={enlarge?"sortinxdirection jupyterswidth":"sortinxdirection"} >
<div className="renwuxiangssi sortinxdirection">
<div><p className="renwuxiangqdiv">任务详情</p></div>
<div><p className="renwuxiangqdivtest ml1 shixunbingbaocun">请将实训题目写在下方并保存</p></div>
</div>
<div className="renwuxiangssit xaxisreverseorder">
{
enlarge===true?
<i className="iconfont icon-suoxiao2 font-20 ml2 ysliconfont" style={{
marginLeft: '30px',
}} onClick={()=>this.onclki(false)}></i>
:
<i className="iconfont icon-fangda font-20 ml2 ysliconfont" style={{
marginLeft: '30px',
}} onClick={()=>this.onclki(true)}></i>
}
<div className="challenbaocun" ><p
className="challenbaocuntest">导入</p>
admin===true||business===true||mysidentity===true?
<div style={{
height: '63px',
}} className={enlarge?"shixunjianjiecballenges edu-back-white intermediatecenter fangdaone":"shixunjianjiecballenges edu-back-white mt20"}>
<div className={enlarge?"sortinxdirection jupyterswidth":"sortinxdirection"} >
<div className="renwuxiangssi sortinxdirection">
<div><p className="renwuxiangqdiv">任务详情</p></div>
<div><p className="renwuxiangqdivtest ml1 shixunbingbaocun">请将实训题目写在下方并保存</p></div>
</div>
<div className="renwuxiangssit xaxisreverseorder">
{
enlarge===true?
<i className="iconfont icon-suoxiao2 font-18 ml2 ysliconfont" style={{
marginLeft: '30px',
}} onClick={()=>this.onclki(false)}></i>
:
<i className="iconfont icon-fangda font-18 ml2 ysliconfont" style={{
marginLeft: '30px',
}} onClick={()=>this.onclki(true)}></i>
}
{/*<div className="challenbaocun" ><p*/}
{/* className="challenbaocuntest">导入</p>*/}
{/*</div>*/}
</div>
</div>
</div>
</div>
:
:
""
)
@ -382,25 +449,25 @@ class Challengesjupyter extends Component {
}
</style>
{
admin===true||business===true||mysidentity===true?
<div>
<div className={"pb47"}>
{
this.state.jupyter_url===null || this.state.jupyter_url===undefined?
(
booljupyterurls===false?
<LoadingSpin></LoadingSpin>
:""
)
:
<iframe src={this.state.jupyter_url} className={enlarge?"fangdatwo":""}
sandbox="allow-same-origin allow-scripts allow-top-navigation " scrolling="no" id="frame"
name="framename" width="100%" height="700" frameBorder="0"
></iframe>
}
</div>
admin===true||business===true||mysidentity===true?
<div>
<div className="pb47">
{
this.state.jupyter_url===null || this.state.jupyter_url===undefined?
(
booljupyterurls===false?
<LoadingSpin></LoadingSpin>
:""
)
:
<iframe src={this.state.jupyter_url} className={enlarge?"fangdatwo":""}
sandbox="allow-same-origin allow-scripts allow-top-navigation " scrolling="no" id="frame"
name="framename" width="100%" height="700" frameBorder="0"
></iframe>
}
</div>
:""
</div>
:""
}
</div>
</div>

@ -184,4 +184,13 @@
.fontneweees {
color: #BBBBBB;
}
.maxfont450{
width: 450px;
max-width:450px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: default;
}

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

Loading…
Cancel
Save