chromesetting
tangjiang 5 years ago
commit cd82ac05e2

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

@ -0,0 +1,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,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 16:02:36
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-13 17:32:33
* @LastEditTime: 2019-12-19 10:47:44
*/
import './index.scss';
import React, { useState, useRef } from 'react';
@ -30,7 +30,7 @@ const ControlSetting = (props) => {
// debuggerCode,
// startDebuggerCode, // 外部存入
onDebuggerCode,
updateCode,
// updateCode,
onSubmitForm
} = props;
const [defaultActiveKey, setDefaultActiveKey] = useState('1'); // 当前选中的tab
@ -84,7 +84,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' }}>

@ -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 15:26:46
*/
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 17:52:31
*/
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 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">{renderRestore}</span>
{/* <Icon className={'code-icon'} type="setting" onClick={handleShowDrawer}/> */}
<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,

@ -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 17:51:19
*/
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) {
@ -51,23 +51,39 @@ 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');
// 处理编辑
@ -117,7 +133,9 @@ function StudentStudy (props) {
<LeftPane />
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane />
<RightPane
updateNotice={handleUpdateNotice}
/>
<div />
</SplitPane>
</SplitPane>
@ -151,6 +169,6 @@ const mapDispatchToProps = (dispatch) => ({
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 15:06:49
*/
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
@ -48,7 +51,8 @@ const RightPane = (props) => {
// 代码块内容变化时
const handleCodeChange = (code) => {
// 保存用户提交的代码块
console.log(code);
// console.log(code);
setEditorCode(code);
if (!timer) {
timer = setInterval(() => {
clearInterval(timer);
@ -69,13 +73,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 +101,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,

@ -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', // 代码调试状态
@ -57,7 +58,9 @@ const types = {
SAVE_JUPYTER_INFO: 'SAVE_JUPYTER_INFO', // 保存 jupyter 信息
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'
CHANGE_JUPYTER_CURRENT_PAGE: 'CHANGE_JUPYTER_CURRENT_PAGE',
SAVE_NOTICE_COUNT: 'SAVE_NOTICE_COUNT', // 保存代码块是否更新
AUTO_UPDATE_CODE: 'AUTO_UPDATE_CODE', // 自动更新代码
}
export default types;

@ -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 14:49:03
*/
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,8 @@ 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 = JSON.parse(hack.description);
Object.assign(hack, {code: tempCode, description: tempDesc});
return {
...state,
hack: Object.assign({}, hack),
@ -121,6 +124,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);
}

Loading…
Cancel
Save