chromesetting
杨树明 5 years ago
parent f8b9f369ec
commit 97a432fa6a

@ -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
# 分班情况
@ -1497,8 +1501,12 @@ class HomeworkCommonsController < ApplicationController
@user = @student_work.user
tip_exception("当前用户无作品可以显示") if @student_work.nil?
# 查询最新一次的查重标识query_id
group_id = @course.course_members.where(user_id: params[:user_id]).pluck(:course_group_id).first
query_id = @homework.homework_group_reviews.where(:course_group_id => group_id).last.try(:query_id)
group_id = @course.students.where(user_id: params[:user_id]).pluck(:course_group_id).first
homework_group_review = @homework.homework_group_reviews.where(:course_group_id => group_id).last || @homework.homework_group_reviews.last
query_id = homework_group_review.try(:query_id)
Rails.logger.info("##################------query_id: #{query_id}")
tip_exception(-1, "query_id有误") unless query_id.present?
results = ReviewService.query_result({user_id: params[:user_id], query_id: query_id})
@shixun = @homework.shixuns.take
if results.status == 0

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

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

@ -1,11 +1,29 @@
//用于嵌入到jupyter pod中的js
//guange 2019.12.18
window.onload=function(){
require(["base/js/namespace"],function(Jupyter) {
Jupyter.notebook.save_checkpoint();
});
window.onload=function(){
// require(["base/js/namespace"],function(Jupyter) {
// Jupyter.notebook.save_checkpoint();
// });
$('.navbar-nav').children().eq(7).css({'display':'none'})
console.log($('.navbar-nav').children().eq(7))
}
// //子目标父窗口接收子窗口发送的消息
// let message = {type: 'open', link:'需要发送的消息'};
// //子窗口向父窗口发送消息,消息中包含我们想跳转的链接
// window.parent.postMessage(message,'需要发送的消息');
// //目标父窗口接收子窗口发送的消息
// 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-19 19:47:32
*/
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,
showOrHideControl,
commitRecordDetail,
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' }}>
@ -131,19 +136,20 @@ const ControlSetting = (props) => {
const mapStateToProps = (state) => {
const {commonReducer, ojForUserReducer} = state;
const {loading, excuteState, submitLoading } = commonReducer;
const {loading, excuteState, submitLoading, showOrHideControl } = commonReducer;
const { commitRecordDetail } = ojForUserReducer;
return {
loading,
submitLoading,
excuteState,
showOrHideControl,
// identifier: user_program_identifier,
commitRecordDetail // 提交详情
};
};
// 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;
@ -52,8 +53,8 @@
height: 56px;
padding-right: 30px;
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;
.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;
}
}

@ -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,10 +69,13 @@ const NewOrEditTask = (props) => {
// 模拟挑战
const imitationChallenge = () => {
// 调用 start 接口, 成功后跳转到模拟页面
startProgramQuestion(identifier, props);
}
// 开始挑战
const startChallenge = () => {
// 调用 start 接口, 成功后跳转到模拟页面
// 调用 start 接口, 成功后跳转到开启实战
// TODO
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,13 +4,14 @@
* @Github:
* @Date: 2019-11-21 09:19:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 11:58:46
* @LastEditTime: 2019-12-19 17:54:28
*/
import './index.scss';
import React, { useState } from 'react';
import { Collapse, Icon, Input, Form, Button, Modal } from 'antd';
import { Collapse, Icon, Input, Form, Button } 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;
@ -31,15 +32,22 @@ const AddTestDemo = (props) => {
// 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);
// }
// })
}
// 输入框值改变时
@ -189,4 +197,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 17:23:10
*/
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="init content"
onContentChange={handleContentChange}
options={quillConfig}
value={ojForm.description}
/>
</div>
</FormItem>
{/* <FormItem
@ -325,6 +331,7 @@ class EditTab extends React.Component {
{getOptions('openOrNot')}
</Select>
</FormItem> */}
</Form>
{/* 添加测试用例 */}

@ -49,6 +49,11 @@
.test_demo_ctx,
.editor_form{
margin: 0 30px;
.ant-form-explain{
margin-top: 5px;
margin-left: -10px;
}
}
.test_demo_title{
display: flex;

@ -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,7 +11,8 @@
align-items: center;
justify-content: space-between;
// padding: 0 30px;
background: #000;
// background: #000;
background: rgba(18,28,36,1);
color: #fff;
}
.code-title,

@ -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;
}
@ -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-19 19:48:20
*/
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,51 @@ 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`);
}
// 处理退出
const handleClickQuit = () => {
// 将控制台关闭
changeShowOrHideControl(false);
props.saveEditorCodeForDetail();
props.history.push('/problems');
}
@ -117,7 +137,9 @@ function StudentStudy (props) {
<LeftPane />
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane />
<RightPane
updateNotice={handleUpdateNotice}
/>
<div />
</SplitPane>
</SplitPane>
@ -146,11 +168,12 @@ const mapDispatchToProps = (dispatch) => ({
saveEditorCodeForDetail: (code) => dispatch(actions.saveEditorCodeForDetail(code)),
// 恢复初始代码
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
changeShowOrHideControl: (flag) => dispatch(actions.changeShowOrHideControl(flag))
});
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,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,6 +13,8 @@
bottom: 0px;
height: 56px;
width: 100%;
box-sizing: border-box;
border-top: 1px solid rgba(244,244,244,1);
// background: pink;
padding: 0 30px;
// background-color: rgba(250,250,250,1);
@ -79,4 +81,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-19 09:22:52
*/
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-19 19:13:05
*/
import React, { useState, useEffect } from 'react';
import {connect} from 'react-redux';
@ -20,8 +20,11 @@ const RightPane = (props) => {
submitUserCode,
input,
hack,
notice,
updateCode,
hadCodeUpdate,
editor_code,
updateNotice,
saveUserInputCode,
restoreInitialCode,
saveUserCodeForInterval
@ -29,6 +32,7 @@ const RightPane = (props) => {
const [editorCode, setEditorCode] = useState('');
let initFlag = true;
useEffect(() => {
if (editor_code) {
setEditorCode(editor_code);
@ -48,7 +52,12 @@ const RightPane = (props) => {
// 代码块内容变化时
const handleCodeChange = (code) => {
// 保存用户提交的代码块
console.log(code);
setEditorCode(code);
// 第一次回填代码内容时不更新;
if (initFlag) {
initFlag = false;
return;
}
if (!timer) {
timer = setInterval(() => {
clearInterval(timer);
@ -69,13 +78,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 +106,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,

@ -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,50 @@ 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 +165,7 @@ class TPMBanner extends Component {
isIE: false
})
}
}
/*
@ -784,7 +795,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 +808,9 @@ class TPMBanner extends Component {
height: 180px !important;
padding-top:35px !important;
}
.ant-popover{
z-index:1000 !important;
}
`
}
</style>

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

@ -34,7 +34,8 @@ class Challenges extends Component {
operationstrue:false,
isSpin:false,
boxoffsetHeigh:0,
opentitletype:true
opentitletype:true,
isopentitletype:"Less",
}
}
@ -52,6 +53,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",
})
}
}
}
}).catch((error) => {
@ -71,17 +79,19 @@ class Challenges extends Component {
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(boxoffsetHeigh<260){
if(boxoffsetHeigh<300){
this.setState({
opentitletype:false,
isopentitletype:"Less",
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
opentitletype:true,
isopentitletype:"greater",
boxoffsetHeigh:boxoffsetHeigh
})
}
console.log(boxoffsetHeigh)
}
}
@ -322,7 +332,8 @@ class Challenges extends Component {
opentitle=()=>{
this.setState({
opentitletype:!this.state.opentitletype
opentitletype:!this.state.opentitletype,
})
}
@ -334,6 +345,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,10 +407,10 @@ 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> : "":""}
@ -407,7 +419,7 @@ class Challenges extends Component {
{
`
#shixunchallengesid{
max-height: 260px;
max-height: 300px;
overflow: hidden;
}
`
@ -427,14 +439,22 @@ class Challenges extends Component {
<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';
@ -23,7 +23,8 @@ class Challengesjupyter extends Component {
booljupyterurls:false,
loading:false,
boxoffsetHeigh:0,
opentitletype:true
opentitletype:true,
isopentitletype:"Less",
}
}
@ -41,6 +42,11 @@ 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",
})
}
}
}
}).catch((error) => {
@ -55,13 +61,15 @@ class Challengesjupyter extends Component {
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(boxoffsetHeigh<260){
if(boxoffsetHeigh<300){
this.setState({
opentitletype:false,
isopentitletype:"Less",
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
isopentitletype:"greater",
opentitletype:true,
boxoffsetHeigh:boxoffsetHeigh
})
}
@ -219,17 +227,17 @@ 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>
{
`
#shixunchallengesid{
max-height: 260px;
max-height: 300px;
overflow: hidden;
}
`
@ -239,9 +247,22 @@ class Challengesjupyter extends Component {
<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?
@ -259,7 +280,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>

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

@ -11,11 +11,9 @@ import NoneData from "../../../courses/coursesPublic/NoneData";
import './Collaborators.css';
const $ = window.$;
const RadioGroup = Radio.Group;
const Search = Input.Search;
class Collaborators extends Component {
constructor(props) {
@ -332,6 +330,7 @@ class Collaborators extends Component {
});
this.updatacomponentDiddata();
this.props.showNotification(response.data.message);
// window.location.reload();
}).catch((error) => {
console.log(error)
});
@ -532,7 +531,7 @@ class Collaborators extends Component {
{
Collaboratorslist.length === 0 ? "" : Collaboratorslist.map((item, key) => {
return (
<Radio key={key} style={radioStyle} value={item.user_id}
<Radio key={key} style={radioStyle} value={item.user_id} defaultChecked={false}
onClick={() => this.addadminredio(item.user_id)}>{item.name}</Radio>
)
})
@ -658,7 +657,7 @@ class Collaborators extends Component {
<a href={item.user.user_url} target="_blank" className="mr20 fl edu-position">
<img alt="用户头像" className="radius" height="48" src={getImageUrl("images/" + item.user.image_url)}
width="48"/>
<span className={item && item.user && item.user.shixun_manager === true&&this.props.power === true ? "ml20 yslusercjz newyslusercjz" : "none "}
<span className={item && item.user && item.user.shixun_manager === true? "ml20 yslusercjz newyslusercjz" : "none "}
// style={{display: this.props.power === false ? "none" : "inline-block"}}
>
<p className="yslusercjztest newyslusercjztest">{item.user.shixun_manager === true ? "创建者" : ""}</p></span>
@ -685,7 +684,7 @@ class Collaborators extends Component {
<p className="hezuozhe630 sortinxdirection space-between">
{/*<p className={item.user.identity===null||item.user.identity===undefined||item.user.identity===""?" font-16 ":"mr20 font-16 w70"}>{item.user.identity}</p>*/}
<p
className={item.user.school_name === null || item.user.school_name === "" ? "" : "mr40 font-16 maxnamewidth150 color888hezuo"}>{item.user.school_name}</p>
className={item.user.school_name === null || item.user.school_name === "" ? "" : " font-16 color888hezuo maxfont450"}>{item.user.school_name}</p>
<p className="fabushixunwidth color888hezuo">发布实训项目&nbsp;&nbsp;<span
className="ml2">{item.user.user_shixuns_count}</span></p>
</p>

@ -218,7 +218,7 @@ class ShixunCardList extends Component {
id="hot"
onClick={(e)=>this.latestHot(e,3)}>最热
</div>
{shixuntype===true?"":<span className={ "fl font-16 bestChoose color-blue" } onClick={(url)=>this.getUser("/shixuns/new")}>+新建实训项目</span>}
{shixuntype===true?"":<span className={ "fr font-16 bestChoose color-blue" } onClick={(url)=>this.getUser("/shixuns/new")}>+新建实训项目</span>}
{/*<div className="fl font-16 bestChoose shixun_repertoire ml20 mt1"*/}
{/*style={{display:upcircle===true?"block":"none"}}*/}

@ -22,7 +22,8 @@ const types = {
SAVE_OJ_FORM: 'SAVE_OJ_FORM', // 保存表单
ADD_TEST_CASE: 'ADD_TEST_CASE', // 添加测试用例
DELETE_TEST_CASE: 'DELETE_TEST_CASE', // 删除测试用例
SAVE_TEST_CASE: '保存测试用例', // 保存测试用例
SAVE_TEST_CASE: 'SAVE_TEST_CASE', // 保存测试用例
SAVE_USE_TEST_CASE_VALUE: 'SAVE_USE_TEST_CASE_VALUE', // 用户自定义测试用例值
CLEAR_JSFORM_STORE: 'CLEAR_JSFORM_STORE', // 清空测试用例
SAVE_EDIT_OJ_FORM_AND_TEST_CASE: 'SAVE_EDIT_OJ_FORM_AND_TEST_CASE', // 保存根据id获取的表单及测试用例值
TEST_CODE_STATUS: 'TEST_CODE_STATUS', // 代码调试状态
@ -58,6 +59,8 @@ const types = {
CHANGE_JUPYTER_URL_STATE: 'CHANGE_JUPYTER_URL_STATE', // 获取url返回的状态值
SAVE_JUPYTER_TPI: 'SAVE_JUPYTER_TPI', // 保存 jupyter tpi
CHANGE_JUPYTER_CURRENT_PAGE: 'CHANGE_JUPYTER_CURRENT_PAGE',
SAVE_NOTICE_COUNT: 'SAVE_NOTICE_COUNT', // 保存代码块是否更新
AUTO_UPDATE_CODE: 'AUTO_UPDATE_CODE', // 自动更新代码
CHANGE_SHOW_DRAWER: 'CHANGE_SHOW_DRAWER',
}

@ -4,12 +4,12 @@
* @Github:
* @Date: 2019-11-27 16:30:50
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 21:15:34
* @LastEditTime: 2019-12-19 19:42:10
*/
import types from "./actionTypes";
// 切换控制台显示与隐藏
export const showOrHideControl = (flag) => {
export const changeShowOrHideControl = (flag) => {
return {
type: types.SHOW_OR_HIDE_CONTROL,
payload: flag

@ -52,7 +52,7 @@ import {
} from './ojForUser';
import {
showOrHideControl,
changeShowOrHideControl,
changeLoadingState,
changeSubmitLoadingStatus,
changePublishLoadingStatus,
@ -98,7 +98,7 @@ export default {
testCaseOutputChange,
debuggerCode,
startProgramQuestion,
showOrHideControl,
changeShowOrHideControl,
changeLoadingState,
getUserCommitRecord,
getUserCommitRecordDetail,

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 13:42:11
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 16:49:42
* @LastEditTime: 2019-12-19 15:11:56
*/
import types from "./actionTypes";
import { Base64 } from 'js-base64';
@ -82,6 +82,20 @@ export const getUserProgramDetail = (identifier, type) => {
payload: data
});
}
// 保存默认测试用例
dispatch({
type: types.SAVE_USE_TEST_CASE_VALUE,
payload: data.test_case || {}
});
// 代码是否更新
let _modify_code = false;
if (data.hack) {
_modify_code = data.hack.modify_code;
}
dispatch({
type: types.SAVE_NOTICE_COUNT,
payload: _modify_code
})
// 保存用户登录信息
dispatch({
type: types.SAVE_USER_INFO,
@ -94,13 +108,29 @@ export const getUserProgramDetail = (identifier, type) => {
export const saveUserCodeForInterval = (identifier, code) => {
return (dispatch) => {
dispatch({
type: types.AUTO_UPDATE_CODE,
payload: true
});
fetchUpdateCode(identifier, {
code: Base64.encode(code)
}).then(res => {
if (res.data.status === 401) {
return;
};
setTimeout(() => {
dispatch({
type: types.AUTO_UPDATE_CODE,
payload: false
})
}, 1000);
console.log('代码保存成功', res);
}).catch(() => {
dispatch({
type: types.AUTO_UPDATE_CODE,
payload: false
})
});
}
}
@ -259,7 +289,11 @@ export const debuggerCode = (identifier,value, type) => {
}).catch(() => {
dispatch({
type: types.TEST_CODE_STATUS,
payload: ''
payload: 'error'
});
dispatch({
type: types.LOADING_STATUS,
payload: false
});
dispatch({
type: types.SUBMIT_LOADING_STATUS,
@ -400,6 +434,10 @@ export const restoreInitialCode = (identifier, msg) => {
message: '提示',
description: msg
});
dispatch({
type: types.SAVE_NOTICE_COUNT,
payload: false
});
}
});
}
@ -412,3 +450,6 @@ export const saveEditorCodeForDetail = (code) => {
payload: code
}
}
// 更新通知状态

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-20 16:35:46
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 16:56:22
* @LastEditTime: 2019-12-19 17:20:48
*/
import types from './actionTypes';
import CONST from '../../constants';
@ -15,7 +15,7 @@ import {
cancelPublicTask
} from '../../services/ojService';
import { Base64 } from 'js-base64';
import { message, notification, Modal } from 'antd';
import { notification } from 'antd';
import { toStore } from 'educoder';
const { jcLabel } = CONST;
// 表单字段映射
@ -160,7 +160,7 @@ export const validateOjForm = (props, type) => {
if (testCases.length === 0) {
hasSuccess = false;
notification['error']({
message: '必填',
message: '提示',
description: '测试用例必须输入!'
});
}
@ -200,7 +200,7 @@ export const validateOjForm = (props, type) => {
let paramsObj = {};
const hack = { // 编程题干
name,
description,
description: JSON.stringify(description),
difficult,
category,
'open_or_not': openOrNot,
@ -449,9 +449,13 @@ export const testCaseInputChange = (value, index) => {
let validate = emptyValidate('input', value)['input'];
if (!validate.errMsg) {
// 唯一性校验
let _errMsg = '';
const {testCases} = getState().ojFormReducer;
const bool = testCases.some((item, i) => {
if (i !== index) {
if (item['input'] === value) {
_errMsg=`与测试用例${index}的输入值重复了,请重新填写`;
}
return item['input'] === value;
} else {
return false;
@ -460,7 +464,7 @@ export const testCaseInputChange = (value, index) => {
if (bool) {
validate = {
validateStatus: 'error',
errMsg: '输入值必须唯一'
errMsg: _errMsg
};
}
}
@ -492,8 +496,12 @@ export const testCaseOutputChange = (value, index) => {
if (!validate.errMsg) {
// 唯一性校验
const {testCases} = getState().ojFormReducer;
let _errMsg = '';
const bool = testCases.some((item, i) => {
if (i !== index) {
if (item['output'] === value) {
_errMsg=`与测试用例${index}的输入值重复了,请重新填写`;
}
return item['output'] === value;
} else {
return false;
@ -502,7 +510,7 @@ export const testCaseOutputChange = (value, index) => {
if (bool) {
validate = {
validateStatus: 'error',
errMsg: '输入值必须唯一'
errMsg: _errMsg
};
}
}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 13:41:48
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 10:09:59
* @LastEditTime: 2019-12-19 20:10:39
*/
import types from "../actions/actionTypes";
import { Base64 } from 'js-base64';
@ -21,7 +21,9 @@ const initialState = {
userTestInput: '', // 用户自定义输入值
recordDetail: {}, // 根据id号获取的记录详情
hack_identifier: '', // 用户界面编辑时
editor_code: '' // 保存编辑代码
editor_code: '', // 保存编辑代码
notice: false, // 通知
hadCodeUpdate: false, // 更新代码
};
const ojForUserReducer = (state = initialState, action) => {
@ -36,7 +38,13 @@ const ojForUserReducer = (state = initialState, action) => {
const { hack, test_case } = action.payload;
const { code }= hack;
let tempCode = Base64.decode(code)
Object.assign(hack, {code: tempCode});
let tempDesc;
try {
tempDesc = JSON.parse(hack.description);
} catch (error) {
tempDesc = hack.description;
}
Object.assign(hack, {code: tempCode, description: tempDesc});
return {
...state,
hack: Object.assign({}, hack),
@ -121,6 +129,21 @@ const ojForUserReducer = (state = initialState, action) => {
...state,
editor_code: action.payload
}
case types.SAVE_USE_TEST_CASE_VALUE:
return {
...state,
userTestInput: action.payload.input
}
case types.SAVE_NOTICE_COUNT:
return {
...state,
notice: action.payload
};
case types.AUTO_UPDATE_CODE:
return {
...state,
hadCodeUpdate: action.payload
};
default:
return state;
}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-20 16:40:32
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 11:54:35
* @LastEditTime: 2019-12-17 16:19:04
*/
import { Base64 } from 'js-base64';
import types from '../actions/actionTypes';
@ -176,7 +176,7 @@ const ojFormReducer = (state = initialState, action) => {
const currentOjForm = {
name, // 任务名称
language,
description,
description: JSON.parse(description),
difficult,
category,
openOrNot: 1,

@ -4,10 +4,11 @@
* @Github:
* @Date: 2019-11-20 10:55:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 17:03:47
* @LastEditTime: 2019-12-17 14:10:37
*/
import axios from 'axios';
import { func } from 'prop-types';
export async function fetchOJList (params) {
console.log('传递的参数: ', params);
@ -125,3 +126,15 @@ export async function fetchUserInfoForNew () {
const url = `/problems/new.json`;
return axios.get(url);
}
// 文件上传
export async function fetchUploadImage (file) {
const url = `/attachments.json`;
return axios.post(url, file)
}
// 根据id号获取图片url
export async function fetchUploadImageUrl (id) {
const url = `/attachments/${id}`;
return axios.get(url);
}

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe CurriculumDirection, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe Curriculum, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe KnowledgePointContainer, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end

@ -1,5 +0,0 @@
require 'rails_helper'
RSpec.describe KnowledgePoint, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end
Loading…
Cancel
Save