完成第一版功能

dev_forge
tangjiang 5 years ago
parent 5661c0d4b2
commit 51ea118369

@ -44,6 +44,8 @@
"immutability-helper": "^2.6.6", "immutability-helper": "^2.6.6",
"install": "^0.12.2", "install": "^0.12.2",
"jest": "20.0.4", "jest": "20.0.4",
"js-base64": "^2.5.1",
"katex": "^0.11.1",
"lodash": "^4.17.5", "lodash": "^4.17.5",
"loglevel": "^1.6.1", "loglevel": "^1.6.1",
"material-ui": "^1.0.0-beta.40", "material-ui": "^1.0.0-beta.40",
@ -58,6 +60,7 @@
"promise": "8.0.1", "promise": "8.0.1",
"prop-types": "^15.6.1", "prop-types": "^15.6.1",
"qs": "^6.6.0", "qs": "^6.6.0",
"quill": "^1.3.7",
"raf": "3.4.0", "raf": "3.4.0",
"rc-form": "^2.1.7", "rc-form": "^2.1.7",
"rc-pagination": "^1.16.2", "rc-pagination": "^1.16.2",

@ -13,6 +13,7 @@
<!--<meta http-equiv="Expires" content="0" />--> <!--<meta http-equiv="Expires" content="0" />-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<!-- <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">--> <!-- <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">-->
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
@ -186,5 +187,6 @@
<!-- <script type="text/javascript" src="https://testeduplus2.educoder.net/assets/kindeditor/kindeditor.js"></script> <!-- <script type="text/javascript" src="https://testeduplus2.educoder.net/assets/kindeditor/kindeditor.js"></script>
<script type="text/javascript" src="/js/create_kindeditor.js"></script> <script type="text/javascript" src="/js/create_kindeditor.js"></script>
<script type="text/javascript" src="https://testeduplus2.educoder.net/javascripts/educoder/edu_application.js"></script> --> <script type="text/javascript" src="https://testeduplus2.educoder.net/javascripts/educoder/edu_application.js"></script> -->
</body> </body>
</html> </html>

@ -668,7 +668,7 @@ class App extends Component {
<Route path="/developer/neworedittask/:id?" component={NewOrEditTask} /> <Route path="/developer/neworedittask/:id?" component={NewOrEditTask} />
<Route path="/developer/studentstudy/:id?" component={StudentStudy} /> <Route path="/developer/studentstudy/:id?" component={StudentStudy} />
<Route path="/developer" component={Developer}/> <Route path="/developer/:id?" component={Developer}/>
<Route exact path="/" <Route exact path="/"
// component={ShixunsHome} // component={ShixunsHome}

@ -4,9 +4,10 @@
* @Github: * @Github:
* @Date: 2019-11-20 23:10:48 * @Date: 2019-11-20 23:10:48
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 10:05:42 * @LastEditTime: 2019-11-28 14:41:42
*/ */
const jcLabel = { const CONST = {
jcLabel: {
name: '任务名称', name: '任务名称',
language: '编程语言', language: '编程语言',
description: '描述', description: '描述',
@ -14,6 +15,103 @@ const jcLabel = {
category: '分类', category: '分类',
openOrNot: '公开程序', openOrNot: '公开程序',
timeLimit: '时间限制' timeLimit: '时间限制'
},
fontSetting: {
title: '代码格式',
type: 'select',
content: [
{
text: '字体大小',
value: [
{
key: 1,
text: '12px',
value: 12
},
{
key: 1,
text: '14px',
value: 14
},
{
key: 1,
text: '16px',
value: 16
},
{
key: 1,
text: '18px',
value: 18
},
{
key: 1,
text: '24px',
value: 24
},
{
key: 1,
text: '30px',
value: 30
} }
]
}
]
},
opacitySetting: {
title: '代码格式',
type: 'label',
content: [
{
text: '字体大小',
value: 'CTRL + S'
},
{
text: '唤出快捷键列表',
value: 'F1/ALT + F1'
},
{
text: '向左缩进',
value: 'CTRL + ['
},
{
text: '向右缩进',
value: 'CTRL + ]'
},
{
text: '跳到匹配的括号',
value: 'CTRL + SHIFT + \\'
},
{
text: '转到行首',
value: 'HOME'
},
{
text: '转到行尾',
value: 'END'
}
]
},
tagBackground: {
1: '#52c41a',
2: '#faad14',
3: '#f5222d'
},
diffText: {
1: '简单',
2: '中等',
3: '困难'
},
reviewResult: {
'-1': '测试用例结果不匹配',
'0': '评测通过',
'1': '',
'2': '评测超时',
'3': '评测pod失败',
'4': '编译失败',
'5': '执行失败'
}
}
export default CONST;
export default jcLabel;

@ -8,13 +8,15 @@
import './index.scss'; import './index.scss';
import React, { PureComponent } from 'react'; import React, { Component } from 'react';
import { Table, Button, Dropdown, Icon, Menu, Card, Input, Select, Tag } from 'antd'; import { Table, Button, Dropdown, Icon, Menu, Card, Input, Select, Tag, Divider } from 'antd';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import actions from '../../redux/actions'; import actions from '../../redux/actions';
import MultipTags from './components/multiptags'; import MultipTags from './components/multiptags';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import CONST from '../../constants';
const {tagBackground, diffText} = CONST;
const { Search } = Input; const { Search } = Input;
const { Option } = Select; const { Option } = Select;
// import reqwest from 'reqwest'; // import reqwest from 'reqwest';
@ -91,32 +93,30 @@ const testMaps = {
category: { category: {
1: '程序设计基础', 1: '程序设计基础',
2: '数据结构与算法' 2: '数据结构与算法'
},
difficult: {
1: '简单',
2: '中等',
3: '困难'
},
type: {
1: '#52c41a',
2: '#faad14',
3: '#f5222d'
} }
} }
/** /**
* 表格列 * 表格列
*/ */
const options = {
title: '操作',
key: 'action',
fixed: 'right',
width: 100,
render: (text, record) => (
<span>
<Button type="primary">
<Link to={`/developer/neworedittask/${record.identifier}`}>编辑</Link>
</Button>
</span>
),
}
const columns = [ const columns = [
// {
// title: '序号',
// dataIndex: 'identifier',
// width: '10%'
// },
{ {
title: '标题', title: '标题',
dataIndex: 'name', dataIndex: 'name',
render: (name, record) => <Link to={`/developer/neworedittask/${record.identifier}`}>{name}</Link> render: (name, record) => <Link style={{ color: '#459be5' }} to={`/developer/studentstudy/${record.identifier}`}>{name}</Link>
}, },
{ {
title: '分类', title: '分类',
@ -129,10 +129,10 @@ const columns = [
title: '难度', title: '难度',
dataIndex: 'difficult', dataIndex: 'difficult',
align: 'center', align: 'center',
width: '10%', width: '15%',
render: (difficult) => { render: (difficult) => {
if (difficult) { if (difficult) {
return <Tag color={testMaps['type'][+difficult]}>{testMaps['difficult'][+difficult]}</Tag> return <Tag color={tagBackground[+difficult]}>{diffText[+difficult]}</Tag>
} else { } else {
return '-'; return '-';
} }
@ -152,10 +152,10 @@ const columns = [
align:'right', align:'right',
width: '10%', width: '10%',
render: val => <span>{`${val}%`}</span> render: val => <span>{`${val}%`}</span>
} },
]; ];
class DeveloperHome extends PureComponent { class DeveloperHome extends Component {
state = { state = {
data: [], data: [],
pagination: { pagination: {
@ -173,11 +173,24 @@ class DeveloperHome extends PureComponent {
category: '', // 分类 category: '', // 分类
'sort_by': '', // 排序 'sort_by': '', // 排序
'sort_direction': '' // 排序方向 'sort_direction': '' // 排序方向
} },
columns: columns
}; };
componentDidMount() { componentDidMount() {
console.log('==============>>>>>>>>>>>>>>>', this.props);
const { isMySource } = this.props;
if (isMySource) {
// this.handleFilterSearch()
this.handleFilterSearch({come_from: 'mine'});
let _columns = columns.concat([options]);
this.setState({
columns: _columns
});
} else {
this.fetchData(); this.fetchData();
}
const {hacks_count} = this.props.ojListReducer; const {hacks_count} = this.props.ojListReducer;
this.setState({ this.setState({
pagination: { pagination: {
@ -186,6 +199,21 @@ class DeveloperHome extends PureComponent {
}); });
} }
componentDidUpdate (nextProps) {
// if (nextProps.isMySource !== this.isMySource) {
// // this.handleFilterSearch({come_from: nextProps.isMySource ? 'mine' : 'all'});
// if (nextProps.isMySource !== 'all') {
// let _columns = columns.concat([options]);
// this.setState({
// columns: _columns
// });
// } else {
// this.setState({
// columns: columns
// })
// }
// }
}
handleTableChange = (pagination, filters, sorter) => { handleTableChange = (pagination, filters, sorter) => {
// console.log(pagination, filters, sorter); // console.log(pagination, filters, sorter);
const {field, order} = sorter; const {field, order} = sorter;
@ -258,13 +286,24 @@ class DeveloperHome extends PureComponent {
// 来源下拉 // 来源下拉
handleOriginMenuClick = (item) => { handleOriginMenuClick = (item) => {
this.handleFilterSearch({come_from: item.key === 'all' ? '' : item.key}); this.handleFilterSearch({come_from: item.key === 'all' ? '' : item.key});
if (item.key !== 'all') {
let _columns = columns.concat([options]);
this.setState({
columns: _columns
});
} else {
this.setState({
columns: columns
})
}
} }
render () { render () {
// const { testReducer, handleClick } = this.props; // const { testReducer, handleClick } = this.props;
const { ojListReducer: {hacks_list, top_data, hacks_count} } = this.props; const { ojListReducer: {hacks_list, top_data, hacks_count} } = this.props;
const {passed_count = 0, simple_count = 0, medium_count = 0, diff_count = 0} = top_data; const {passed_count = 0, simple_count = 0, medium_count = 0, diff_count = 0} = top_data;
// console.log( '======>>>>>>', ojListReducer ); const { columns } = this.state;
return ( return (
<div className="developer-list"> <div className="developer-list">
<div className="ant-spin-container"> <div className="ant-spin-container">
@ -329,13 +368,14 @@ class DeveloperHome extends PureComponent {
*/ */
const mapStateToProps = (state, ownProps) => ({ const mapStateToProps = (state, ownProps) => ({
testReducer: state.testReducer, testReducer: state.testReducer,
ojListReducer: state.ojListReducer ojListReducer: state.ojListReducer,
isMySource: state.commonReducer.isMySource
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
handleClick: () => dispatch(actions.toggleTodo()), handleClick: () => dispatch(actions.toggleTodo()),
fetchOJList: (params) => dispatch(actions.getOJList(params)) fetchOJList: (params) => dispatch(actions.getOJList(params)),
}); });
export default connect( export default connect(

@ -0,0 +1,154 @@
/*
* @Description: 右侧代码块控制台
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 16:02:36
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 16:48:50
*/
import './index.scss';
import React, { useState, useRef } from 'react';
import { Tabs, Button, Icon } from 'antd';
import { connect } from 'react-redux';
import InitTabCtx from '../initTabCtx';
import ExecResult from '../execResult';
import actions from '../../../../redux/actions';
const { TabPane } = Tabs;
const ControlSetting = (props) => {
const {
inputValue,
loading,
submitLoading,
identifier,
excuteState,
commitRecordDetail,
changeLoadingState,
changeSubmitLoadingStatus,
showOrHideControl,
// debuggerCode
updateCode,
onSubmitForm
} = props;
const [defaultActiveKey, setDefaultActiveKey] = useState('1'); // 当前选中的tab
const [showTextResult, setShowTextResult] = useState(false); // 是否点击控制台按钮
const formRef = useRef(null);
const classNames = `control_tab ${showTextResult ? 'move_up move_up_final' : 'move_down_final'}`;
// 切换tab
const handleTabChange = (key) => {
setDefaultActiveKey(key);
}
// 显示/隐藏tab
const handleShowControl = () => {
setShowTextResult(!showTextResult);
showOrHideControl(!showTextResult);
}
// 调试代码
const handleTestCode = (e) => {
// console.log(formRef.current.handleTestCodeFormSubmit);
// 调出控制台界面
setShowTextResult(true);
showOrHideControl(true);
formRef.current.handleTestCodeFormSubmit(() => {
setDefaultActiveKey('2');
});
}
// 提交
const handleSubmit = (e) => {
e.preventDefault();
changeSubmitLoadingStatus(true)
onSubmitForm && onSubmitForm();
}
// 处理调度代码
const handleDebuggerCode = (values) => {
// 改变状态值
changeLoadingState(true);
// 调用代码保存接口, 成功后再调用调试接口
updateCode(identifier, values, 'debug');
// 调用调试接口
// debuggerCode(identifier, values);
}
return (
<div className="pane_control_area">
<Tabs
className={classNames}
activeKey={defaultActiveKey}
tabBarStyle={{ backgroundColor: '#000', color: '#fff' }}
onChange={handleTabChange}
>
<TabPane tab={'自定义测试用例'} key={'1'} style={{ height: '280px', overflowY: 'auto' }}>
<InitTabCtx
inputValue={inputValue}
wrappedComponentRef={(form) => formRef.current = form}
onDebuggerCode={handleDebuggerCode}
/>
</TabPane>
<TabPane tab={'代码执行结果'} key={'2'} style={{ height: '280px', overflowY: 'auto' }}>
<ExecResult
excuteState={excuteState}
excuteDetail={commitRecordDetail}
/>
</TabPane>
</Tabs>
<div className="pane_control_opts">
<Button
type="link"
style={{ color: '#fff' }}
onClick={handleShowControl}>
控制台 <Icon type={ showTextResult ? "down" : "up" } />
</Button>
<p>
<Button ghost
loading={loading}
style={{ marginRight: '10px', color: '#28BD8B', borderColor: '#28BD8B' }}
onClick={handleTestCode}
disabled={!identifier}
>调试代码</Button>
<Button
loading={submitLoading}
type="primary"
onClick={handleSubmit}
>
{/* {props.identifier ? '更新' : '提交'} */}
提交
</Button>
</p>
</div>
</div>
);
}
const mapStateToProps = (state) => {
const {commonReducer, ojForUserReducer} = state;
const { loading, excuteState, submitLoading } = commonReducer;
const { user_program_identifier, commitRecordDetail } = ojForUserReducer;
return {
loading,
submitLoading,
excuteState,
identifier: user_program_identifier,
commitRecordDetail // 提交详情
};
};
// changeSubmitLoadingStatus
const mapDispatchToProps = (dispatch) => ({
showOrHideControl: (flag) => dispatch(actions.showOrHideControl(flag)),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag)),
changeSubmitLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag)),
debuggerCode: (identifier, values) => dispatch(actions.debuggerCode(identifier, values)),
// inputValue 输入值
updateCode: (identifier, inputValue, type) => dispatch(actions.updateCode(identifier, inputValue, type))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ControlSetting);

@ -0,0 +1,103 @@
.pane_control_area{
position: absolute;
bottom: 0;
width: 100%;
// height: 56px;
.control_tab{
position: absolute;
bottom: -325px;
width: 100%;
// transition: all .2s;
opacity: 0;
// animation: .3s ease-in-out move_up;
// &.active{
// bottom: 0;
// opacity: 1;
// }
&.move_up{
animation: move_up .3s ease-in;
}
&.move_up_final {
bottom: 0;
opacity: 1;
}
&.move_down{
animation: move_down .3s ease-in-out;
}
&.move_down_final{
bottom: -325px;
opacity: 0;
}
}
}
.ant-tabs-bar{
padding: 0 10px;
margin: 0px;
border-bottom: transparent;
}
.ant-tabs-ink-bar{
bottom: 1px;
}
// .tab_ctx_area.pos_center{
// background: #222;
// }
.pane_control_opts{
display: flex;
justify-content: space-between;
align-items: center;
z-index: 20;
height: 56px;
padding-right: 30px;
padding-left: 10px;
background: #000;
}
.setting_drawer{
.setting_h2{
line-height: 50px;
}
.setting_desc{
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.flex_item{
line-height: 32px;
font-size: 12px;
}
}
}
@keyframes move_up {
0%{
opacity: 0;
// bottom: -325px;
}
90%{
opacity: 0.5;
// bottom: 0px;
}
100%{
opacity: 1;
bottom: 0;
}
}
@keyframes move_down{
0%{
opacity: 1;
bottom: 0
}
10%{
opacity: .2;
}
20%{
opacity: 0;
}
100%{
opacity: 0;
bottom: -325px;
}
}

@ -0,0 +1,134 @@
/*
* @Description: 执行结果
* @Author: tangjiang
* @Github:
* @Date: 2019-11-28 08:44:54
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 15:14:42
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
import { Icon } from 'antd';
import CONST from '../../../../constants';
const {reviewResult} = CONST;
function ExecResult (props) {
const { excuteState, excuteDetail } = props;
// 指定渲染初始, 加载中, 加载完成页面内容
const renderInit = () => (
<div className={'excute_result_area excute_flex_center'}>
<span className={'init_ctx'}>请先点击调试代码运行您的代码</span>
</div>
);
const renderLoading = () => (
<div className={'excute_result_area excute_flex_center'}>
<span className={'loading_ctx'}>
<Icon className={'ctx_icon'} type="loading"/>
<span>加载中...</span>
</span>
</div>
);
const readerLoaded = () => (
<div className={'excute_result_area excute_flex_center'}>
<span className={'loaded_ctx'}>
<Icon className={'ctx_icon'} type="loading"/>
<span>加载完成</span>
</span>
</div>
);
const renderFinish = () => {
const {
error_line,
error_msg,
execute_memory,
execute_time,
input,
output,
status,
expected_output
} = codeResult;
const excuteHeader = (state) => {
const review_class = state === 0 ? `excute_suc` : `excute_err`;
return (
<p className={'excute_head_area'}>
<span className={'excute_head_txt'}>执行结果: </span>
<span className={review_class}>{reviewResult[`${state}`]}</span>
</p>
)
}
const excuteCtx = (state) => {
if (state === 0) {
return (
<React.Fragment>
<p className={'result_info_style'}>输入: {input}</p>
<p className={'result_info_style'}>输出: {output}</p>
</React.Fragment>
);
} else if (state === 4){
return (
<p className={'result_info_style'}>
{error_msg}
</p>
)
} else if (state === -1) {
return (
<React.Fragment>
<p className={'result_info_style'}>输入: {input}</p>
<p className={'result_info_style'}>输出: {output}</p>
<p className={'result_info_style'}>预期输出: {expected_output}</p>
</React.Fragment>
)
} else if (state === 5) {
return (
<React.Fragment>
<p className={'result_info_style'}> 执行出错信息: {error_msg}</p>
<p className={'result_info_style'}>最后执行的输入: {input}</p>
</React.Fragment>
)
}
}
return (
<div className={'excute_result_info'}>
{excuteHeader(status)}
{excuteCtx(status)}
</div>
);
};
// 渲染状态
const [renderCtx, setRenderCtx] = useState(() => {
return function () {
return renderInit();
}
});
// 提交记录详情
const [codeResult, setCodeResult] = useState({})
// 渲染状态变化时渲染相应的内容
useEffect(() => {
if ('loading' === excuteState) {
setRenderCtx(() => (renderLoading));
} else if ('loaded' === excuteState) {
setRenderCtx(() => (readerLoaded));
} else if ('finish' === excuteState) {
setRenderCtx(() => (renderFinish));
}
}, [excuteState]);
// 提交详情变化时
useEffect(() => {
console.log('提交记录详情=====>>>>>', excuteDetail);
setCodeResult(excuteDetail);
}, [excuteDetail]);
return (
<React.Fragment>
{renderCtx()}
</React.Fragment>
)
}
export default ExecResult;

@ -0,0 +1,47 @@
.excute_result_area{
display: flex;
height: 224px;
width: 100%;
&.excute_flex_center{
align-items: center;
justify-content: center;
}
.init_ctx{
color: #666666;
}
.loading_ctx,
.loaded_ctx{
display: flex;
flex-direction: column;
color: #1890ff;
.ctx_icon{
font-size: 40px;
margin-bottom: 10px;
}
}
}
.excute_result_info{
padding: 20px 30px;
color: #fff;
height: 220px;
/* overflow-y: auto; */
overflow-y: auto;
.result_info_style{
word-wrap: break-word;
color: #ccc;
}
.excute_head_area{
line-height: 30px;
.excute_suc{
color: #28BD8B;
}
.excute_err{
color: #E51C24;
}
}
}

@ -0,0 +1,92 @@
/*
* @Description: 自定义测试化用例
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 19:46:14
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 23:49:21
*/
import './index.scss';
import React, { useState, useEffect, useRef, useImperativeHandle, forwardRef } from 'react';
import { Form, Input} from 'antd';
const FormItem = Form.Item;
const { TextArea } = Input;
/**
* @description 初始化测试用例: 当有inputValue值时, 显示表单输入框否则显示文本提示信息
* @param {*} props
* props: {
* inputValue: '' // 初始值
* onDebuggerCode: func // 点击调试代码执行函数
* }
*/
function InitTabCtx (props, ref) {
// useImperativeHandle // 让子组件只暴露一定的api给父组件
const tabRef = useRef(null);
const { inputValue, onDebuggerCode } = props;
useImperativeHandle(ref, () => ({
handleTestCodeFormSubmit: (cb) => {
console.log('父组件调用我啦~~~~~~~~~');
_handleTestCodeFormSubmit(cb);
}
}));
// 渲染文本提示信息
const renderText = () => (<span className={'ctx_default'}>请在这里添加测试用例点击调试代码时将从这里读取输入来测试你的代码...</span>);
// 渲染表单信息
const renderForm = () => {
const {form: { getFieldDecorator } } = props;
return (
<Form className={'user_case_form'}>
<FormItem
className={'input_area flex_l'}
label='输入'
>
{
getFieldDecorator('input', {
rules: [
{ required: true, message: '输入值不能为空'}
],
initialValue: inputValue
})(<TextArea rows={5} />)
}
</FormItem>
</Form>
)
}
// 初始渲染内容
const [renderCtx, setRenderCtx] = useState(() => {
return function () {
return renderText();
};
});
// 输入值变化时更新渲染内容
useEffect(() => {
setRenderCtx(() => {
return renderForm;
});
}, [inputValue]);
const _handleTestCodeFormSubmit = (cb) => {
const {form} = props;
form.validateFields((err, values) => {
if (!err) { // 表单验证通过时,调用测试接口
cb && cb(); // 调用回调函数,切换 tab
onDebuggerCode && onDebuggerCode(values);
}
});
}
return(
<div ref={tabRef}>
{renderCtx()}
</div>
)
}
export default Form.create()(forwardRef(InitTabCtx));

@ -0,0 +1,50 @@
.tab_ctx_area{
display: flex;
height: 100%;
color: #666;
font-size: 14px;
&.pos_start{
justify-content: flex-start;
}
&.pos_center{
justify-content: center;
align-items: center;
}
&.pos_end{
justify-content: flex-end;
}
.ctx_default{
margin: 10px 20px;
}
.ctx_loading,
.ctx_loaded{
display: flex;
position: relative;
flex-direction: column;
top: -20px;
color: #1890ff;
.ctx_icon{
font-size: 40px;
margin-bottom: 10px;
}
}
}
.user_case_form{
display: flex;
align-items: flex-start;
margin-top: 20px;
.input_area{
flex: 1;
.ant-form-item-required{
color: #fff;
}
}
.flex_l{
padding: 0 10px 0 20px;
color: #fff;
}
.flex_r{
padding: 0 20px 0 10px;
}
}

@ -0,0 +1,127 @@
/*
* @Description: 显示tab中的内容
* @Author: tangjiang
* @Date: 2019-11-18 10:43:03
* @Last Modified by: tangjiang
* @Last Modified time: 2019-11-18 11:35:12
*/
import './index.scss';
import React, { PureComponent } from 'react';
import { Icon, Form, Input } from 'antd';
import { connect } from 'react-redux';
import actions from '../../../../redux/actions';
const FormItem = Form.Item;
const { TextArea } = Input;
const tabCtx = (ctx, props) => (<p {...props}>{ctx}</p>);
const renderUserCase = (ctx, position, props) => {
const {form: { getFieldDecorator }, testCases = []} = props;
const testCase = testCases[0] || {}; // 获取第一个测试用例
return (
<Form className={'user_case_form'}>
<FormItem
className={'input_area flex_l'}
label='输入'
>
{
getFieldDecorator('input', {
rules: [
{ required: true, message: '输入值不能为空'}
],
initialValue: testCase.input
})(<TextArea rows={5} />)
}
</FormItem>
{/* <FormItem
className={'input_area flex_r'}
label="输出">
{
getFieldDecorator('output', {
rules: [
{required: true, message: '输出值不能为空'}
],
initialValue: testCase.output
})(<Input />)
}
</FormItem> */}
</Form>
)
};
const defaultCtx = (<span className={'ctx_default'}>请在这里添加测试用例点击调试代码时将从这里读取输入来测试你的代码...</span>)
const loadingCtx = (<span className={'ctx_loading'}><Icon className={'ctx_icon'} type="loading"/>加载中...</span>);
const loadedCtx = (<span className={'ctx_loaded'}><Icon className={'ctx_icon'} type="loading"/>加载完成</span>);
const maps = {
// default: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_default pos_${position}`}>{ctx}</p>),
// loading: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_loading pos_${position}`}>{ctx}</p>),
// loaded: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_loaded pos_${position}`}>{ctx}</p>),
// final: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_final pos_${position}`}>{ctx}</p>)
// 无测试用例时,显示提示信息, ctx: 显示的信息, position: 显示信息的位置
default: (ctx, position) => tabCtx(defaultCtx, { className: `tab_ctx_area tab_ctx_default pos_${position}` }),
// 调度代码加载中
loading: (ctx, position) => tabCtx(loadingCtx, { className: `tab_ctx_area tab_ctx_loading pos_${position}` }),
// 调度代码加载完成
loaded: (ctx, position) => tabCtx(loadedCtx, { className: `tab_ctx_area tab_ctx_loaded pos_${position}` }),
// 显示结果
final: (ctx, position) => tabCtx(ctx, { className: `tab_ctx_area tab_ctx_final pos_${position}` }),
// 显示自定义测试用例面板
userCase: (ctx, position, props) => renderUserCase(ctx, position, props)
}
class InitTabCtx extends PureComponent {
state = {
ctx: '',
position: ''
}
handleTestCodeFormSubmit = (cb) => {
const {form, debuggerCode} = this.props;
console.log(debuggerCode);
form.validateFields((err, values) => {
if (!err) { // 表单验证通过时,调用测试接口
cb && cb(); // 调用回调函数,切换 tab
console.log('表单值:', values);
debuggerCode(values);
}
});
}
componentDidMount () {
const { testCases = []} = this.props;
this.setState({
status: testCases.length > 0 ? 'userCase' : 'default'
});
}
render () {
/**
* @param state 当前状态 default: 显示提示信息 init: 加载初始内容 loading: 加载中 loaded: 加载完成 final: 显示最终内容
* @param position: start | cetner | end
* @param testCase: 自定义测试用例
* @returns
*/
const { testCodeStatus} = this.props;
const { ctx, position } = this.state;
// console.log('===>>>>> 测试用例集合: ', testCases);
return(
<React.Fragment>
{ maps[testCodeStatus](ctx, position, this.props) }
</React.Fragment>
)
}
}
const mapStateToProps = (state) => {
const ojFormReducer = state.ojFormReducer;
return {
testCases: ojFormReducer.testCases, // 测试用例
testCodeStatus: ojFormReducer.testCodeStatus
};
};
const mapDispatchToProps = (dispatch) => ({
debuggerCode: (value) => dispatch(actions.debuggerCode(value))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form.create()(InitTabCtx));

@ -0,0 +1,72 @@
/*
* @Description: 编辑器侧边栏设置信息
* @Author: tangjiang
* @Github:
* @Date: 2019-11-25 17:50:33
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 14:40:36
*/
import React from 'react';
import { Select } from 'antd';
const { Option } = Select;
const SettingDrawer = (props) => {
/**
* title: '', // 一级标题
* type: '', // 类型: 目录 select 和 文本
* content: [] // 显示的内容 { text: '' , value: string | [{ key: 1, value: '', text: '' }] }
*/
const {title, type = 'label', content = [] } = props;
const handleFontSize = (value) => {
const {onChangeFontSize} = props;
// console.log('fong size change: ', value);
onChangeFontSize && onChangeFontSize(value);
}
const renderCtx = (title, content = [], type = 'label') => {
const result = content.map((ctx, index) => {
const subText = ctx.text;
const value = ctx.value;
let renderResult = '';
if (typeof value === 'string') {
renderResult = (
<div className={'setting_desc'} key={`lab_${index}`}>
<span className={'flex_item'}>{subText}</span>
<span className={'flex_item'}>{ctx.value}</span>
</div>
);
} else if (Array.isArray(value)) {
if (type === 'select') {
const child = ctx.value.map((opt, i) => (
<Option key={opt.key || `${opt.value}`} value={opt.value}>
{opt.text}
</Option>
));
renderResult = (
<div className={'setting_desc'} key={`sel_${index}`}>
<span className={'flex_item'}>{ctx.text}</span>
<Select className={'flex_item'} style={{ width: '100px'}} onChange={handleFontSize}>
{child}
</Select>
</div>
);
}
}
return renderResult;
});
return (
<React.Fragment>
<h2 className={'setting_h2'}>{title}</h2>
{ result }
</React.Fragment>
);
}
return (
<div className={'setting_area'}>
{renderCtx(title, content, type)}
</div>
)
}
export default SettingDrawer;

@ -8,7 +8,7 @@
import './index.scss'; import './index.scss';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
const numberal = require('numeral') const numberal = require('numeral');
export default class MultipTags extends PureComponent { export default class MultipTags extends PureComponent {

@ -0,0 +1,123 @@
/*
* @Description: 抽取代码编辑器
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 15:02:52
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 12:39:39
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
import { Icon, Drawer } from 'antd';
import { connect } from 'react-redux';
import MonacoEditor from '@monaco-editor/react';
import SettingDrawer from '../../components/monacoSetting';
import CONST from '../../../../constants';
import actions from '../../../../redux/actions';
const { fontSetting, opacitySetting } = CONST;
const MyMonacoEditor = (props) => {
const {
language,
code,
showOrHideControl,
saveUserInputCode
} = props;
const [showDrawer, setShowDrawer] = useState(false); // 控制配置滑框
const [editCode, setEditCode] = useState('');
// const [curLang, setCurLang] = useState('C');
const [fontSize, setFontSize] = useState(12);
const [ height, setHeight ] = useState('calc(100% - 112px)');
const editorRef = useRef(null);
useEffect(() => {
if (code) {
setEditCode(code);
}
}, [code]);
useEffect(() => {
setHeight(showOrHideControl ? 'calc(100% - 382px)' : 'calc(100% - 112px)');
}, [showOrHideControl]);
// 控制侧边栏设置的显示
const handleShowDrawer = () => {
setShowDrawer(true);
}
// 关闭设置
const handleDrawerClose = () => {
setShowDrawer(false);
}
// 侧边栏改变字体大小
const handleFontSizeChange = (value) => {
setFontSize(value);
}
// 文本框内容变化时,记录文本框内容
const handleEditorChange = (origin, monaco) => {
editorRef.current = monaco; // 获取当前monaco实例
setEditCode(origin); // 保存编辑器初始值
editorRef.current.onDidChangeModelContent(e => { // 监听编辑器内容的变化
// TODO 需要优化 节流
const val = editorRef.current.getValue();
setEditCode(val);
// 值一变化保存当前代码值
saveUserInputCode(val);
});
}
// 配置编辑器属性
const editorOptions = {
selectOnLineNumbers: true,
automaticLayout: true,
fontSize: `${fontSize}px`
}
return (
<React.Fragment>
<div className={"monaco_editor_area"}>
<div className="code_title">
<span>已保存</span>
<Icon className={'code-icon'} type="setting" onClick={handleShowDrawer}/>
</div>
<MonacoEditor
height={height}
width="100%"
language={language && language.toLowerCase()}
value={editCode}
options={editorOptions}
theme="dark"
editorDidMount={handleEditorChange}
/>
</div>
<Drawer
className={'setting_drawer'}
placement="right"
closable={false}
onClose={handleDrawerClose}
visible={showDrawer}
>
<SettingDrawer {...fontSetting} onChangeFontSize={handleFontSizeChange}/>
<SettingDrawer {...opacitySetting}/>
</Drawer>
</React.Fragment>
)
}
const mapStateToProps = (state) => {
const { showOrHideControl } = state.commonReducer;
return {
showOrHideControl
}
};
const mapDispatchToProps = (dispatch) => ({
saveUserInputCode: (code) => dispatch(actions.saveUserInputCode(code)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyMonacoEditor);

@ -0,0 +1,15 @@
.monaco_editor_area{
height: 100%;
.code_title{
display: flex;
align-items: center;
justify-content: space-between;
background: #000;
color: #fff;
height: 56px;
padding: 0 30px;
.code-icon{
cursor: pointer;
}
}
}

@ -0,0 +1,62 @@
/*
* @Description: 文字 | 图标 + 数字样式
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 10:58:37
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 14:22:38
*/
import './index.scss';
import React from 'react';
import { Icon } from 'antd';
const numberal = require('numeral');
const TextNumber = (props) => {
/**
* text: 显示的文本信息
* number: 显示的数字
* position: 位置 vertical | horizontal (默认)
* type: 内容 文字或图标
* onIconClick: 点击图标时的回调函数
*/
const { text, number, position = 'horizontal', type = 'label', onIconClick} = props;
const handleIconClick = () => {
onIconClick && onIconClick();
}
const renderNumb = () => {
let tempNumb = number;
if ((tempNumb || tempNumb === 0) && (typeof Number(tempNumb) === 'number')) {
tempNumb = numberal(tempNumb).format('0,0');
return (
<span className={'numb_value'}>{tempNumb}</span>
)
}
return '';
}
const renderCtx = () => {
if (type === 'icon') { // 图标加文字时
return (
<div className={`text_number_area text_icon_numb flex_${position}`}>
<Icon onClick={handleIconClick} type={text} className={'numb_icon'}></Icon>
{renderNumb()}
</div>
)
} else {
return (
<div className={`text_number_area text_label_numb flex_${position}`}>
<span className={'text_label'}>{text}</span>
{renderNumb()}
</div>
)
}
}
return (
<React.Fragment>
{renderCtx()}
</React.Fragment>
);
}
export default TextNumber;

@ -0,0 +1,43 @@
.text_number_area{
display: flex;
}
.flex_vertical{
flex-direction: column;
justify-content: space-around;
align-items: center;
}
.flex_horizontal{
flex-direction: row;
}
.text_label_numb,
.text_icon_numb{
line-height: 18px;
vertical-align: top;
.numb_value{
font-size: 14px;
}
}
.text_label_numb{
.numb_value{
color: #333333;
}
.text_label{
font-size: 12px;
}
}
.text_icon_numb{
.numb_icon{
font-size: 16px;
margin-right: 5px;
color: #333333;
cursor: pointer;
}
.numb_value{
color: #999999;
}
}

@ -1,201 +0,0 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-21 16:57:34
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-21 20:11:05
*/
import React from 'react';
import { getUrl } from 'educoder';
const $ = window.$;
const path = getUrl("/editormd/lib/")
const imageUrl = `/api/attachments.json`;
// 恢复数据
function md_rec_data(k,mdu,id, editor){
if(window.sessionStorage.getItem(k+mdu) !== null){
editor.setValue(window.sessionStorage.getItem(k+mdu));
md_clear_data(k,mdu,id);
}
}
// 保存数据
function md_add_data(k,mdu,d){
window.sessionStorage.setItem(k+mdu,d);
}
// 清空保存的数据
function md_clear_data(k,mdu,id){
window.sessionStorage.removeItem(k+mdu);
var id1 = "#e_tip_"+id;
var id2 = "#e_tips_"+id;
if(k == 'content'){
$(id2).html("");
}else{
$(id1).html("");
}
}
function md_elocalStorage(editor,mdu,id){
if (window.sessionStorage){
var oc = window.sessionStorage.getItem('content'+mdu);
if(oc !== null ){
console.log("#e_tips_"+id)
$("#e_tips_"+id).data('editor', editor);
var h = '您上次有已保存的数据,是否<a style="cursor: pointer;" class="link-color-blue" onclick="md_rec_data(\'content\',\''+ mdu + '\',\'' + id + '\')">恢复</a> ? / <a style="cursor: pointer;" class="link-color-blue" onclick="md_clear_data(\'content\',\''+ mdu + '\',\'' + id + '\')">不恢复</a>';
$("#e_tips_"+id).html(h);
}
setInterval(function() {
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var s = d.getSeconds();
h = h < 10 ? '0' + h : h;
m = m < 10 ? '0' + m : m;
s = s < 10 ? '0' + s : s;
if(editor.getValue().trim() != ""){
md_add_data("content",mdu,editor.getValue());
var id1 = "#e_tip_"+id;
var id2 = "#e_tips_"+id;
$(id1).html(" 数据已于 " + h + ':' + m + ':' + s +" 保存 ");
$(id2).html("");
}
},10000);
}else{
$("#e_tip_"+id).after('您的浏览器不支持localStorage.无法开启自动保存草稿服务,请升级浏览器!');
}
}
function create_editorMD(id, width, high, placeholder, imageUrl, callback) {
var editorName = window.editormd(id, {
width: width,
height: high,
path: path, // "/editormd/lib/"
syncScrolling: "single",
tex: true,
tocm: true,
emoji: true,
taskList: true,
codeFold: true,
searchReplace: true,
htmlDecode: "style,script,iframe",
sequenceDiagram: true,
autoFocus: false,
toolbarIcons: function () {
// Or return editormd.toolbarModes[name]; // full, simple, mini
// Using "||" set icons align right.
return ["bold", "italic", "|", "list-ul", "list-ol", "|", "code", "code-block", "|", "testIcon", "testIcon1", '|', "image", "table", '|', "watch", "clear"]
},
toolbarCustomIcons: {
testIcon: "<a type=\"inline\" class=\"latex\" ><div class='zbg'></div></a>",
testIcon1: "<a type=\"latex\" class=\"latex\" ><div class='zbg_latex'></div></a>"
},
//这个配置在simple.html中并没有但是为了能够提交表单使用这个配置可以让构造出来的HTML代码直接在第二个隐藏的textarea域中方便post提交表单。
saveHTMLToTextarea: true,
// 用于增加自定义工具栏的功能可以直接插入HTML标签不使用默认的元素创建图标
dialogMaskOpacity: 0.6,
placeholder: placeholder,
imageUpload: true,
imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "JPG", "JPEG", "GIF", "PNG", "BMP", "WEBP"],
imageUploadURL: imageUrl,//url
onload: function () {
// this.previewing();
$("#" + id + " [type=\"latex\"]").bind("click", function () {
editorName.cm.replaceSelection("```latex");
editorName.cm.replaceSelection("\n");
editorName.cm.replaceSelection("\n");
editorName.cm.replaceSelection("```");
var __Cursor = editorName.cm.getDoc().getCursor();
editorName.cm.setCursor(__Cursor.line - 1, 0);
});
$("#" + id + " [type=\"inline\"]").bind("click", function () {
editorName.cm.replaceSelection("`$$$$`");
var __Cursor = editorName.cm.getDoc().getCursor();
editorName.cm.setCursor(__Cursor.line, __Cursor.ch - 3);
editorName.cm.focus();
});
$("[type=\"inline\"]").attr("title", "行内公式");
$("[type=\"latex\"]").attr("title", "多行公式");
md_elocalStorage(editorName, `answers__${id}`, "Memoanswers");
callback && callback()
}
});
return editorName;
}
function initialEditorMd(id, width, height, placeholder, imageUrl, callback) {
const testEditor = window.editormd(id, {
width: width,
height: height,
path : path,
// theme : "light",
// previewTheme : "light",
// editorTheme : "pastel-on-dark",
markdown : '/test code',
codeFold : true,
//syncScrolling : false,
saveHTMLToTextarea : true, // 保存 HTML 到 Textarea
searchReplace : true,
//watch : false, // 关闭实时预览
htmlDecode : "style,script,iframe|on*", // 开启 HTML 标签解析,为了安全性,默认不开启
//toolbar : false, //关闭工具栏
//previewCodeHighlight : false, // 关闭预览 HTML 的代码块高亮,默认开启
emoji : true,
taskList : true,
tocm : true, // Using [TOCM]
tex : true, // 开启科学公式TeX语言支持默认关闭
flowChart : true, // 开启流程图支持,默认关闭
sequenceDiagram : true, // 开启时序/序列图支持,默认关闭,
//dialogLockScreen : false, // 设置弹出层对话框不锁屏全局通用默认为true
//dialogShowMask : false, // 设置弹出层对话框显示透明遮罩层全局通用默认为true
//dialogDraggable : false, // 设置弹出层对话框不可拖动全局通用默认为true
//dialogMaskOpacity : 0.4, // 设置透明遮罩层的透明度全局通用默认值为0.1
//dialogMaskBgColor : "#000", // 设置透明遮罩层的背景颜色,全局通用,默认为#fff
imageUpload : true,
imageFormats : ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
imageUploadURL : imageUrl,
onload : function() {
$("#" + id + " [type=\"latex\"]").bind("click", function () {
testEditor.cm.replaceSelection("```latex");
testEditor.cm.replaceSelection("\n");
testEditor.cm.replaceSelection("\n");
testEditor.cm.replaceSelection("```");
var __Cursor = testEditor.cm.getDoc().getCursor();
testEditor.cm.setCursor(__Cursor.line - 1, 0);
});
$("#" + id + " [type=\"inline\"]").bind("click", function () {
testEditor.cm.replaceSelection("`$$$$`");
var __Cursor = testEditor.cm.getDoc().getCursor();
testEditor.cm.setCursor(__Cursor.line, __Cursor.ch - 3);
testEditor.cm.focus();
});
$("[type=\"inline\"]").attr("title", "行内公式");
$("[type=\"latex\"]").attr("title", "多行公式");
}
});
return testEditor;
}
export default class MEditor extends React.Component {
componentDidMount () {
// create_editorMD('editormd_area', '100%', 400, '', imageUrl);
initialEditorMd('editormd_area', '100%', 400, '', imageUrl);
}
render () {
return (
<div id="editormd_area"></div>
)
}
}

@ -12,39 +12,66 @@ import SplitPane from 'react-split-pane';// import { Form } from 'antd';
import { Button, Icon } from 'antd'; import { Button, Icon } from 'antd';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import LeftPane from './leftpane'; import LeftPane from './leftpane';
import RightPane from './rightpane/index'; import RightPane from './rightpane';
// import RightPane from './rightpane/index';
import actions from '../../../redux/actions'; import actions from '../../../redux/actions';
const NewOrEditTask = (props) => { const NewOrEditTask = (props) => {
const {
publishLoading,
handlePublish,
changeSubmitLoadingStatus,
changePublishLoadingStatus,
identifier,
} = props;
// 表单提交 // 表单提交
const handleSubmitForm = (code) => { const handleSubmitForm = (code) => {
props.saveOjFormCode(code); // 保存代码块值 props.saveOjFormCode(code); // 保存代码块值
props.handleFormSubmit(); // 提交表单 // TODO
// identifier 存在时
if (props.identifier) {
props.handleUpdateOjForm(props);
} else {
props.handleFormSubmit(props); // 提交表单
}
}; };
useEffect(() => { useEffect(() => {
console.log('获取路由参数: ====', props.match.params);
// console.log('获取路由参数: ====', props.match.params);
const id = props.match.params.id; const id = props.match.params.id;
// 保存OJForm的id号指明是编辑还是新增 // 保存OJForm的id号指明是编辑还是新增
props.saveOJFormId(id); props.saveOJFormId(id);
if (id) { // id号即 identifier if (id) { // id号即 identifier
// TODO id 存在时, 编辑, 获取 store 中的记录数 // TODO id 存在时, 编辑, 获取 store 中的记录数
// props.getOJFormById(id); props.getOJFormById(id);
} else { } else {
// 清空store中的测试用例集合 // 清空store中的测试用例集合
props.clearOJFormStore(); props.clearOJFormStore();
} }
return () => {}
}, []); }, []);
const handleClickPublish = () => {
// console.log('public has click');
changePublishLoadingStatus(true);
handlePublish(props, 'publish');
}
return ( return (
<div className={'new_add_task_wrap'}> <div className={'new_add_task_wrap'}>
<div className={'task_header'}> <div className={'task_header'}>
<Link to="/" className={'header_btn'} > <Link to="/developer" className={'header_btn'} >
<Icon type="left" style={{ marginRight: '5px'}}/>后退 <Icon type="left" style={{ marginRight: '5px'}}/>后退
</Link> </Link>
<span className={'header_title'}>标题内容</span> <span className={'header_title'}>{props.name || ''}</span>
<Button className={`header_btn`} type="primary">立即发布</Button> <Button
style={{ display: identifier ? 'none' : 'block'}}
loading={publishLoading}
className={`header_btn`}
type="primary"
onClick={handleClickPublish}>立即发布</Button>
</div> </div>
<div className="split-pane-area"> <div className="split-pane-area">
<SplitPane split="vertical" minSize={200} maxSize={-200} defaultSize="50%"> <SplitPane split="vertical" minSize={200} maxSize={-200} defaultSize="50%">
@ -61,21 +88,35 @@ const NewOrEditTask = (props) => {
) )
} }
const mapStateToProps = (state) => ({ const mapStateToProps = (state) => {
const { ojForm, identifier } = state.ojFormReducer;
}); const { publishLoading } = state.commonReducer;
return {
name: ojForm.name,
identifier,
publishLoading
}
};
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
// 保存提交的代码值 // 保存提交的代码值
saveOjFormCode: (value) => dispatch(actions.saveOjFormCode(value)), saveOjFormCode: (value) => dispatch(actions.saveOjFormCode(value)),
// 表单提交时,调用表单验证功能 // 表单提交时,调用表单验证功能
handleFormSubmit: () => dispatch(actions.validateOjForm()), handleFormSubmit: (props) => dispatch(actions.validateOjForm(props)),
// 发布表单
handlePublish: (props, type) => dispatch(actions.validateOjForm(props, type)),
// 更新OJForm
handleUpdateOjForm: (props) => dispatch(actions.validateOjForm(props)),
// 根据id号获取表单信息 // 根据id号获取表单信息
getOJFormById: (id) => dispatch(actions.getOJFormById(id)), getOJFormById: (id) => dispatch(actions.getOJFormById(id)),
// 保存 OJ form id值 // 保存 OJ form id值
saveOJFormId: (id) => dispatch(actions.saveOJFormId(id)), saveOJFormId: (id) => dispatch(actions.saveOJFormId(id)),
// 清空测试用例的集合 // 清空测试用例的集合
clearOJFormStore: () => dispatch(actions.clearOJFormStore()), clearOJFormStore: () => dispatch(actions.clearOJFormStore()),
// 按钮状态
changeSubmitLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag)),
//
changePublishLoadingStatus: (flag) => dispatch(actions.changePublishLoadingStatus(flag))
}); });
export default connect( export default connect(

@ -4,26 +4,27 @@
* @Github: * @Github:
* @Date: 2019-11-21 09:19:38 * @Date: 2019-11-21 09:19:38
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 19:45:48 * @LastEditTime: 2019-11-26 15:47:06
*/ */
import './index.scss'; import './index.scss';
import React, { useEffect, useState } from 'react'; import React, { useState } from 'react';
import { Collapse, Icon, Input, Form, Button, Modal } from 'antd'; import { Collapse, Icon, Input, Form, Button, Modal } from 'antd';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import actions from '../../../../../redux/actions';
const { Panel } = Collapse; const { Panel } = Collapse;
const { TextArea } = Input; const { TextArea } = Input;
const FormItem = Form.Item; const FormItem = Form.Item;
const AddTestDemo = (props) => { const AddTestDemo = (props) => {
const { const {
form: { getFieldDecorator },
onSubmitTest, onSubmitTest,
onDeleteTest, onDeleteTest,
testCase, testCase,
key key,
ojTestCaseValidate,
index
} = props; } = props;
const [isEditor, setIsEditor] = useState(false); const [isEditor, setIsEditor] = useState(false); // 是否是编辑
const [loading, setLoading] = useState(false);
// console.log('测试用例属性: ====>>>>', props); // console.log('测试用例属性: ====>>>>', props);
// 删除操作 // 删除操作
const handleDeletePanel = (e) => { const handleDeletePanel = (e) => {
@ -42,6 +43,20 @@ const AddTestDemo = (props) => {
}) })
} }
// 输入框值改变时
const handleInputChange = (e) => {
const { index, testCaseInputChange } = props;
const value = e.target.value;
testCaseInputChange(value, index);
}
// 输出值改变时
const handleOutputChange = (e) => {
const { index, testCaseOutputChange } = props;
const value = e.target.value;
testCaseOutputChange(value, index);
}
// 右侧删除图标 // 右侧删除图标
const genExtra = () => ( const genExtra = () => (
<Icon <Icon
@ -74,12 +89,12 @@ const AddTestDemo = (props) => {
} else { } else {
// TODO 调用修改测试用例接口 // TODO 调用修改测试用例接口
setIsEditor(false); // 保存后 设置 false setIsEditor(false); // 保存后 设置 false
setLoading(true);
} }
} }
// 渲染提交按钮
const renderSubmitBtn = () => { const renderSubmitBtn = () => {
const { identifier, testCase } = props; const { identifier, testCase, loading } = props;
// console.log('========', identifier); // console.log('========', identifier);
// 1. 新增时,不显示按钮 // 1. 新增时,不显示按钮
if (identifier) { if (identifier) {
@ -100,42 +115,43 @@ const AddTestDemo = (props) => {
} }
} }
// 指定文本框是否可编辑 /**
* 文本输入框可编辑的情况
* 1. 新增时
* 2. isAdd false isEditor 为true
* @param {*} testCase
*/
const isDisabled = (testCase) => { const isDisabled = (testCase) => {
return testCase.isAdd && !isEditor; return !testCase.isAdd && !isEditor;
}; };
const {input = {}, output = {}} = (ojTestCaseValidate[index] = {});
return ( return (
<Collapse className={'collapse_area'}> <Collapse className={'collapse_area'}>
<Panel header={`测试用例${testCase.position}`} extra={genExtra()} key={key}> <Panel header={`测试用例${testCase.position}`} extra={genExtra()} key={key}>
<Form> <Form>
<FormItem <FormItem
label='输入' label={<span className={'label_text'}>输入</span>}
validateStatus={input.validateStatus}
help={input.errMsg}
colon={ false }
> >
{ <TextArea
getFieldDecorator('input', { rows={5}
rules: [ value={testCase.input}
{ required: true, message: '输入值不能为空'} onChange={handleInputChange}
], disabled={isDisabled(testCase)}/>
initialValue: testCase && testCase.input
})(<TextArea rows={5} disabled={isDisabled(testCase)}/>)
}
</FormItem> </FormItem>
<FormItem label="输出"> <FormItem
{ label={<span className={'label_text'}>输出</span>}
getFieldDecorator('output', { validateStatus={output.validateStatus}
rules: [ help={output.errMsg}
{required: true, message: '输出值不能为空'} colon={ false }
], >
initialValue: testCase && testCase.output <Input
})(<Input disabled={isDisabled(testCase)}/>) value={testCase.output}
} onChange={handleOutputChange}
disabled={isDisabled(testCase)}/>
</FormItem> </FormItem>
{/* <FormItem style={{ textAlign: 'right' }}>
<Button style={{ marginRight: '20px' }} onClick={handleReset}>取消</Button>
<Button type="primary" onClick={handleSubmit}>保存</Button>
</FormItem> */}
{renderSubmitBtn()}
</Form> </Form>
</Panel> </Panel>
</Collapse> </Collapse>
@ -143,17 +159,20 @@ const AddTestDemo = (props) => {
} }
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const ojFormReducer = state.ojFormReducer; const {identifier, loading, ojTestCaseValidate} = state.ojFormReducer;
return { return {
identifier: ojFormReducer.identifier identifier,
loading,
ojTestCaseValidate
} }
}; };
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
testCaseOutputChange: (value, index) => dispatch(actions.testCaseOutputChange(value, index)),
testCaseInputChange: (value, index) => dispatch(actions.testCaseInputChange(value, index))
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(Form.create()(AddTestDemo)); )(AddTestDemo);

@ -4,25 +4,24 @@
* @Github: * @Github:
* @Date: 2019-11-20 10:35:40 * @Date: 2019-11-20 10:35:40
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 19:10:53 * @LastEditTime: 2019-11-27 19:04:03
*/ */
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.bubble.css';
import 'quill/dist/quill.snow.css';
import './index.scss'; import './index.scss';
// import 'katex/dist/katex.css';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Form, Input, Select, InputNumber, Button } from 'antd'; import { Form, Input, Select, InputNumber, Button } from 'antd';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import AddTestDemo from './AddTestDemo'; import AddTestDemo from './AddTestDemo';
import QuillEditor from '../../../quillEditor';
import actions from '../../../../../redux/actions'; import actions from '../../../../../redux/actions';
import jcLabel from '../../../../../constants'; import CONST from '../../../../../constants';
import MEditor from '../../../meditor/MEditor'; const {jcLabel} = CONST;
// import { getUrl } from 'educoder';
// const path = getUrl("/editormd/lib/")
// const mEditor = require('editor.md');
// console.log(mEditor);
const FormItem = Form.Item; const FormItem = Form.Item;
const { Option } = Select; const { Option } = Select;
const { TextArea } = Input;
const maps = { const maps = {
language: [ language: [
{ title: 'C', key: 'C' }, { title: 'C', key: 'C' },
@ -50,30 +49,19 @@ class EditTab extends PureComponent {
super(props); super(props);
this.editorRef = React.createRef(); this.editorRef = React.createRef();
} }
componentDidMount () {
// this.createEditorMd('editormd_section', '100%', 400, '', '', '', path);
}
// 改变任务名称 // 改变任务名称
handleNameChange = (e) => { handleNameChange = (e) => {
const value = e.target.value; const value = e.target.value;
const { this.props.validateOJName(value);
validateOJName,
// validateOjLanguage,
// validateOjDescription,
// validateOjDifficult,
// validateOjTimeLimit,
// validateOjCategory,
// validateOpenOrNot
} = this.props;
validateOJName(value);
} }
// 改变语言 // 改变语言
handleLanguageChange = (value) => { handleLanguageChange = (value) => {
this.props.validateOjLanguage(value); this.props.validateOjLanguage(value);
} }
// 改变描述信息 // 改变描述信息
handleChangeDescription = (e) => { handleChangeDescription = (value) => {
const value = e.target.value; // console.log('获取的编辑器内容为: ', value);
this.props.validateOjDescription(value); this.props.validateOjDescription(value);
} }
// 改变难易度 // 改变难易度
@ -101,7 +89,9 @@ class EditTab extends PureComponent {
addTestCase, // 添加测试用例 addTestCase, // 添加测试用例
deleteTestCase, // 删除测试用例 deleteTestCase, // 删除测试用例
} = this.props; } = this.props;
console.log('当前位置: ', position); // console.log('当前位置: ', position);
// console.log('OJForm: ', ojForm);
// console.log('当前位置: ', testCases);
// 表单label // 表单label
const myLabel = (name, subTitle) => { const myLabel = (name, subTitle) => {
if (subTitle) { if (subTitle) {
@ -143,6 +133,7 @@ class EditTab extends PureComponent {
onSubmitTest={handleSubmitTest} onSubmitTest={handleSubmitTest}
onDeleteTest={handleDeleteTest} onDeleteTest={handleDeleteTest}
testCase={item} testCase={item}
index={i}
/> />
)); ));
}; };
@ -164,7 +155,7 @@ class EditTab extends PureComponent {
// oDiv.scrollTop = 99999; // oDiv.scrollTop = 99999;
} }
return ( return (
<div className={'editor_area'} ref={this.editorRef}> <div className={'editor_area'}>
<Form className={'editor_form'}> <Form className={'editor_form'}>
<FormItem <FormItem
className={`input_area flex_60`} className={`input_area flex_60`}
@ -188,7 +179,7 @@ class EditTab extends PureComponent {
help={ojFormValidate.language.errMsg} help={ojFormValidate.language.errMsg}
colon={ false } colon={ false }
> >
<Select onChange={this.handleLanguageChange} defaultValue={`${ojForm.language}`}> <Select onChange={this.handleLanguageChange} value={`${ojForm.language}`}>
{getOptions('language')} {getOptions('language')}
</Select> </Select>
</FormItem> </FormItem>
@ -199,7 +190,12 @@ class EditTab extends PureComponent {
help={ojFormValidate.description.errMsg} help={ojFormValidate.description.errMsg}
colon={ false } colon={ false }
> >
<TextArea rows={5} onChange={this.handleChangeDescription}/> <QuillEditor
style={{ height: '300px' }}
placeholder="init content"
onEditorChange={this.handleChangeDescription}
htmlCtx={ojForm.description}
/>
</FormItem> </FormItem>
<FormItem <FormItem
className={`input_area flex_50 flex_50_left`} className={`input_area flex_50 flex_50_left`}
@ -208,7 +204,7 @@ class EditTab extends PureComponent {
help={ojFormValidate.difficult.errMsg} help={ojFormValidate.difficult.errMsg}
colon={ false } colon={ false }
> >
<Select onChange={this.handleChangeDifficult} defaultValue={`${ojForm.difficult}`}> <Select onChange={this.handleChangeDifficult} value={`${ojForm.difficult}`}>
{getOptions('difficult')} {getOptions('difficult')}
</Select> </Select>
</FormItem> </FormItem>
@ -219,7 +215,7 @@ class EditTab extends PureComponent {
help={ojFormValidate.timeLimit.errMsg} help={ojFormValidate.timeLimit.errMsg}
colon={ false } colon={ false }
> >
<InputNumber min={0} style={{ width: '100%' }} onChange={this.handleTimeLimitChange}/> <InputNumber value={ojForm.timeLimit} min={0} style={{ width: '100%' }} onChange={this.handleTimeLimitChange}/>
</FormItem> </FormItem>
<FormItem <FormItem
className={`input_area flex_50 flex_50_left`} className={`input_area flex_50 flex_50_left`}
@ -228,7 +224,7 @@ class EditTab extends PureComponent {
help={ojFormValidate.category.errMsg} help={ojFormValidate.category.errMsg}
colon={ false } colon={ false }
> >
<Select onChange={this.handleChangeCategory} defaultValue={`${ojForm.category}`}> <Select onChange={this.handleChangeCategory} value={`${ojForm.category}`}>
{getOptions('category')} {getOptions('category')}
</Select> </Select>
</FormItem> </FormItem>
@ -239,7 +235,7 @@ class EditTab extends PureComponent {
help={ojFormValidate.openOrNot.errMsg} help={ojFormValidate.openOrNot.errMsg}
colon={ false } colon={ false }
> >
<Select onChange={this.handleChangeOpenOrNot} defaultValue={`${ojForm.openOrNot}`}> <Select onChange={this.handleChangeOpenOrNot} value={`${ojForm.openOrNot}`}>
{getOptions('openOrNot')} {getOptions('openOrNot')}
</Select> </Select>
</FormItem> </FormItem>

@ -1,6 +1,5 @@
.editor_area{ .editor_area{
height: calc(100vh - 110px);
overflow-y: auto;
padding: 20px 0; padding: 20px 0;
.editor_form{ .editor_form{
display: flex; display: flex;

@ -23,7 +23,7 @@ const LeftPane = () => {
const tabArrs = [ const tabArrs = [
{ title: '编辑', key: 'editor', content: (<EditorTab />) }, { title: '编辑', key: 'editor', content: (<EditorTab />) },
{ title: '预览', key: 'prev', content: (<PrevTab />) }, { title: '预览', key: 'prev', content: (<PrevTab />) },
{ title: '提交记录', key: 'commit', content: (<CommitTab />) }, // { title: '提交记录', key: 'commit', content: (<CommitTab />) },
]; ];
const tabs = tabArrs.map((tab) => { const tabs = tabArrs.map((tab) => {

@ -1,13 +1,25 @@
.split-pane-left{ // .split-pane-left{
.ant-tabs-nav-wrap{ // .ant-tabs-nav-wrap{
padding: 0 30px; // padding: 0 30px;
} // }
.ant-tabs-bar{ // .ant-tabs-bar{
margin: 0; // margin: 0;
} // }
// .ant-tabs-tabpane{ // // .ant-tabs-tabpane{
// padding-top: 10px; // // padding-top: 10px;
// // height: calc(100vh - 110px);
// // overflow: auto;
// // }
// .ant-form-item-control{
// line-height: 1;
// }
// .editor_area,
// .prev_area{
// height: calc(100vh - 110px); // height: calc(100vh - 110px);
// overflow: auto; // overflow-y: auto;
// padding: 20px 0;
// }
// } // }
} @import '../../split_pane_resizer.scss';

@ -1,15 +1,57 @@
import React, { PureComponent } from 'react'; /*
// import connect from 'react-redux'; * @Description: 代码预览页面
* @Author: tangjiang
* @Github:
* @Date: 2019-11-24 10:09:55
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 19:30:51
*/
import './index.scss';
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import {Empty} from 'antd';
import QuillEditor from '../../../quillEditor';
const PrevTab = (props) => {
// const { } = props;
const [desc, setDesc] = useState('');
class PrevTab extends PureComponent { useEffect(() => {
setDesc(props.description);
}, [props.description]);
state = {} const renderHtml = () => {
render () { if (!desc) {
return ( return (
<h2>预览页</h2> <div className={'no_result'}>
<Empty />
</div>
);
} else {
return (
<div className={'render_html'} dangerouslySetInnerHTML={{ __html: desc }}></div>
) )
} }
} }
return (
<div className={`prev_area`}>
{/* {renderHtml()} */}
{/* <div dangerouslySetInnerHTML={{ __html: desc }}></div> */}
<QuillEditor
style={{ height: 'calc(100% - 45px)', overflowY: 'auto' }}
options={[]}
readOnly={true}
htmlCtx={props.description}/>
</div>
)
}
const mapStateToProps = (state) => {
const { ojForm } = state.ojFormReducer;
return {
description: ojForm.description
}
}
// export default connect()(PrevTab); export default connect(
export default PrevTab; mapStateToProps
)(PrevTab);

@ -0,0 +1,11 @@
.no_result{
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.render_html{
padding: 20px 30px;
}

@ -8,42 +8,62 @@
import './index.scss'; import './index.scss';
import React, { Fragment, useState, useRef } from 'react'; import React, { Fragment, useState, useRef, useEffect } from 'react';
import { Icon, Drawer, Tabs, Button, notification } from 'antd'; import { Icon, Drawer, Tabs, Button, notification } from 'antd';
// import MonacoEditor from 'react-monaco-editor'; import _ from 'lodash';
import MonacoEditor, { DiffEditor } from '@monaco-editor/react'; import MonacoEditor from '@monaco-editor/react';
import { connect } from 'react-redux';
import InitTabCtx from './initTabCtx'; import InitTabCtx from './initTabCtx';
import SettingDrawer from '../../components/monacoSetting';
import CONST from '../../../../constants';
import actions from '../../../../redux/actions';
const { fontSetting, opacitySetting } = CONST;
const { TabPane } = Tabs; const { TabPane } = Tabs;
const tabsArrs = [
{
title: '自定义测试用例',
key: '1',
content: '这是自定测试用例内容'
},
{
title: '代码执行结果',
key: '2',
content: '这是自定代码执行结果'
}
];
const RightPaneCode = (props) => { const RightPaneCode = (props) => {
const [showDrawer, setShowDrawer] = useState(false); // 控制配置滑框 const [showDrawer, setShowDrawer] = useState(false); // 控制配置滑框
const [defaultActiveKey, setDefaultActiveKey] = useState('1'); // 当前选中的tab const [defaultActiveKey, setDefaultActiveKey] = useState('1'); // 当前选中的tab
const [showTextResult, setShowTextResult] = useState(false); // 是否点击控制台按钮 const [showTextResult, setShowTextResult] = useState(false); // 是否点击控制台按钮
const [editCode, setEditCode] = useState('console.log("test")'); // monaco编辑器内容 const [editCode, setEditCode] = useState(()=> {
return '#include <stdio.h>';
}); // monaco编辑器内容
const [language, setLanguage] = useState('C')
const [fontSize,setFontSize] = useState(12);
const editorRef = useRef(null); // 编辑器ref const editorRef = useRef(null); // 编辑器ref
useEffect(() => {
if (props.language) {
// console.log('当前输入的代码:', editCode);
// console.log('当前输入的语言:', props.language);
setLanguage(props.language)
}
}, [props.language]);
useEffect(() => {
console.log('测试用例更改');
}, [props.testCases]);
useEffect(() => {
console.log('编辑器内容变化时');
}, [editCode]);
// 监听store中编辑器内容变化
useEffect(() => {
setEditCode(props.code);
}, [props.code]);
// 打开设置 // 打开设置
const handleShowDrawer = () => { const handleShowDrawer = (e) => {
e.preventDefault();
setShowDrawer(true); setShowDrawer(true);
} }
// 关闭设置 // 关闭设置
const handleDrawerClose = () => { const handleDrawerClose = (e) => {
e.preventDefault();
setShowDrawer(false); setShowDrawer(false);
} }
@ -57,21 +77,20 @@ const RightPaneCode = (props) => {
setShowTextResult(!showTextResult); setShowTextResult(!showTextResult);
} }
// 沉浸tab内容 // 侧边栏改变字体大小
const tabs = tabsArrs.map((tab) => ( const handleFontSizeChange = (value) => {
<TabPane tab={tab.title} key={tab.key} style={{ height: '280px', overflowY: 'auto' }}> setFontSize(value);
{/* <InitTabCtx ctx={tab.content} state="loaded" position="center"/> */} }
</TabPane>
));
// 文本框内容变化时,记录文本框内容 // 文本框内容变化时,记录文本框内容
const handleEditorChange = (origin, monaco) => { const handleEditorChange = (origin, monaco) => {
editorRef.current = monaco; // 获取当前monaco实例 editorRef.current = monaco; // 获取当前monaco实例
setEditCode(origin); // 保存编辑器初始值 setEditCode(origin); // 保存编辑器初始值
editorRef.current.onDidChangeModelContent(e => { // 监听编辑器内容的变化 editorRef.current.onDidChangeModelContent(e => { // 监听编辑器内容的变化
// TODO 需要优化 节流
const val = editorRef.current.getValue(); const val = editorRef.current.getValue();
setEditCode(val); setEditCode(val);
// 保存代码值 // 保存当前代码
props.saveOjFormCode(val);
}); });
} }
@ -86,18 +105,29 @@ const RightPaneCode = (props) => {
editorRef.current.focus(); editorRef.current.focus();
return; return;
} }
props.changePublishLoadingStatus(true);
const { onSubmitForm } = props; const { onSubmitForm } = props;
onSubmitForm(editCode); onSubmitForm(editCode);
} }
// 调试测试代码
// const handleTestCode = () => {
// // 打开控制台信息
// setShowTextResult(true);
// this.formRef.handleTestCodeFormSubmit(() => {
// // 当验证通过后 切换tab 到代码执行结果
// setDefaultActiveKey('2');
// });
// }
// 控制台点击时 添加active属性 // 控制台点击时 添加active属性
const classNames = showTextResult ? 'control_tab active' : 'control_tab'; const classNames = `control_tab ${showTextResult ? 'move_up move_up_final' : 'move_down_final'}`;
// 配置编辑器属性 // 配置编辑器属性
const editorOptions = { const editorOptions = {
selectOnLineNumbers: true, selectOnLineNumbers: true,
automaticLayout: true, automaticLayout: true,
fontSize: '16px' fontSize: `${fontSize}px`
} }
// 返回渲染值 // 返回渲染值
@ -105,7 +135,6 @@ const RightPaneCode = (props) => {
<Fragment> <Fragment>
<div className={'right_pane_code_wrap'}> <div className={'right_pane_code_wrap'}>
<div className={'code-title'}> <div className={'code-title'}>
{/* <span>ts.js</span> */}
<span>已保存</span> <span>已保存</span>
<Icon className={'code-icon'} type="setting" onClick={handleShowDrawer}/> <Icon className={'code-icon'} type="setting" onClick={handleShowDrawer}/>
</div> </div>
@ -113,7 +142,7 @@ const RightPaneCode = (props) => {
<MonacoEditor <MonacoEditor
height={showTextResult ? 'calc(100% - 382px)' : 'calc(100% - 112px)'} height={showTextResult ? 'calc(100% - 382px)' : 'calc(100% - 112px)'}
width="100%" width="100%"
language="typescript" language={language.toLowerCase()}
value={editCode} value={editCode}
options={editorOptions} options={editorOptions}
theme="dark" theme="dark"
@ -127,32 +156,62 @@ const RightPaneCode = (props) => {
tabBarStyle={{ backgroundColor: '#000', color: '#fff' }} tabBarStyle={{ backgroundColor: '#000', color: '#fff' }}
onChange={handleTabChange} onChange={handleTabChange}
> >
{tabs} <TabPane tab={'自定义测试用例'} key={'1'} style={{ height: '280px', overflowY: 'auto' }}>
<InitTabCtx wrappedComponentRef={(form) => this.formRef = form }/>
</TabPane>
<TabPane tab={'代码执行结果'} key={'2'} style={{ height: '280px', overflowY: 'auto' }}>
<h2>代码执行结果</h2>
</TabPane>
</Tabs> </Tabs>
<div className="pane_control_opts"> <div className="pane_control_opts">
<Button type="link" style={{ color: '#fff' }} onClick={handleShowControl}>控制台 <Icon type={ showTextResult ? "down" : "up" } /></Button> <Button type="link" style={{ color: '#fff' }} onClick={handleShowControl}>控制台 <Icon type={ showTextResult ? "down" : "up" } /></Button>
<p> <p>
<Button ghost style={{ marginRight: '10px', color: '#28BD8B', borderColor: '#28BD8B' }}>调试代码</Button> {/* <Button ghost
style={{ marginRight: '10px', color: '#28BD8B', borderColor: '#28BD8B' }}
onClick={handleTestCode}
disabled={!props.identifier || props.testCases.length === 0}
>调试代码</Button> */}
<Button <Button
loading={props.submitLoading}
type="primary" type="primary"
onClick={handleSubmit} onClick={handleSubmit}
>提交</Button> >{props.identifier ? '更新' : '提交'}</Button>
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<Drawer <Drawer
className={'setting_drawer'}
placement="right" placement="right"
closable={false} closable={false}
onClose={handleDrawerClose} onClose={handleDrawerClose}
visible={showDrawer} visible={showDrawer}
> >
<p>Some contents...</p> <SettingDrawer {...fontSetting} onChangeFontSize={handleFontSizeChange}/>
<p>Some contents...</p> <SettingDrawer {...opacitySetting}/>
<p>Some contents...</p>
</Drawer> </Drawer>
</Fragment> </Fragment>
); );
} }
export default RightPaneCode; const mapStateToProps = (state) => {
const { ojForm, testCases, identifier, code } = state.ojFormReducer;
const { submitLoading } = state.commonReducer;
return {
language: ojForm.language,
testCases,
identifier,
code,
submitLoading
}
};
const mapDispatchToProps = (dispatch) => ({
saveOjFormCode: (code) => dispatch(actions.saveOjFormCode(code)),
changePublishLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag))
});
//
export default connect(
mapStateToProps,
mapDispatchToProps
)(RightPaneCode);

@ -46,12 +46,27 @@
position: absolute; position: absolute;
bottom: -325px; bottom: -325px;
width: 100%; width: 100%;
transition: all .2s; // transition: all .2s;
opacity: 0; opacity: 0;
&.active{ // animation: .3s ease-in-out move_up;
// &.active{
// bottom: 0;
// opacity: 1;
// }
&.move_up{
animation: move_up .3s ease-in;
}
&.move_up_final {
bottom: 0; bottom: 0;
opacity: 1; opacity: 1;
} }
&.move_down{
animation: move_down .3s ease-in-out;
}
&.move_down_final{
bottom: -325px;
opacity: 0;
}
} }
} }
.pane_control_opts{ .pane_control_opts{
@ -75,3 +90,50 @@
z-index: 20; z-index: 20;
} }
} }
.setting_drawer{
.setting_h2{
line-height: 50px;
}
.setting_desc{
display: flex;
justify-content: space-between;
margin-bottom: 10px;
.flex_item{
line-height: 32px;
font-size: 12px;
}
}
}
@keyframes move_up {
0%{
opacity: 0;
// bottom: -325px;
}
90%{
opacity: 0.5;
// bottom: 0px;
}
100%{
opacity: 1;
bottom: 0;
}
}
@keyframes move_down{
0%{
opacity: 1;
bottom: 0
}
10%{
opacity: .2;
}
20%{
opacity: 0;
}
100%{
opacity: 0;
bottom: -325px;
}
}

@ -7,10 +7,46 @@
*/ */
import './index.scss'; import './index.scss';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Icon } from 'antd'; import { Icon, Form, Input } from 'antd';
import Editor from "@monaco-editor/react"; import { connect } from 'react-redux';
import actions from '../../../../../redux/actions';
const FormItem = Form.Item;
const { TextArea } = Input;
const tabCtx = (ctx, props) => (<p {...props}>{ctx}</p>); const tabCtx = (ctx, props) => (<p {...props}>{ctx}</p>);
const renderUserCase = (ctx, position, props) => {
const {form: { getFieldDecorator }, testCases = []} = props;
const testCase = testCases[0] || {}; // 获取第一个测试用例
return (
<Form className={'user_case_form'}>
<FormItem
className={'input_area flex_l'}
label='输入'
>
{
getFieldDecorator('input', {
rules: [
{ required: true, message: '输入值不能为空'}
],
initialValue: testCase.input
})(<TextArea rows={5} />)
}
</FormItem>
{/* <FormItem
className={'input_area flex_r'}
label="输出">
{
getFieldDecorator('output', {
rules: [
{required: true, message: '输出值不能为空'}
],
initialValue: testCase.output
})(<Input />)
}
</FormItem> */}
</Form>
)
};
const defaultCtx = (<span className={'ctx_default'}>请在这里添加测试用例点击调试代码时将从这里读取输入来测试你的代码...</span>)
const loadingCtx = (<span className={'ctx_loading'}><Icon className={'ctx_icon'} type="loading"/>加载中...</span>); const loadingCtx = (<span className={'ctx_loading'}><Icon className={'ctx_icon'} type="loading"/>加载中...</span>);
const loadedCtx = (<span className={'ctx_loaded'}><Icon className={'ctx_icon'} type="loading"/>加载完成</span>); const loadedCtx = (<span className={'ctx_loaded'}><Icon className={'ctx_icon'} type="loading"/>加载完成</span>);
const maps = { const maps = {
@ -19,7 +55,7 @@ const maps = {
// loaded: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_loaded pos_${position}`}>{ctx}</p>), // loaded: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_loaded pos_${position}`}>{ctx}</p>),
// final: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_final pos_${position}`}>{ctx}</p>) // final: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_final pos_${position}`}>{ctx}</p>)
// 无测试用例时,显示提示信息, ctx: 显示的信息, position: 显示信息的位置 // 无测试用例时,显示提示信息, ctx: 显示的信息, position: 显示信息的位置
default: (ctx, position) => tabCtx(ctx, { className: `tab_ctx_area tab_ctx_default pos_${position}` }), default: (ctx, position) => tabCtx(defaultCtx, { className: `tab_ctx_area tab_ctx_default pos_${position}` }),
// 调度代码加载中 // 调度代码加载中
loading: (ctx, position) => tabCtx(loadingCtx, { className: `tab_ctx_area tab_ctx_loading pos_${position}` }), loading: (ctx, position) => tabCtx(loadingCtx, { className: `tab_ctx_area tab_ctx_loading pos_${position}` }),
// 调度代码加载完成 // 调度代码加载完成
@ -27,12 +63,35 @@ const maps = {
// 显示结果 // 显示结果
final: (ctx, position) => tabCtx(ctx, { className: `tab_ctx_area tab_ctx_final pos_${position}` }), final: (ctx, position) => tabCtx(ctx, { className: `tab_ctx_area tab_ctx_final pos_${position}` }),
// 显示自定义测试用例面板 // 显示自定义测试用例面板
// case: (ctx, position) => userCase: (ctx, position, props) => renderUserCase(ctx, position, props)
}
class InitTabCtx extends PureComponent {
state = {
ctx: '',
position: ''
} }
export default class InitTabCtx extends PureComponent { handleTestCodeFormSubmit = (cb) => {
const {form, debuggerCode} = this.props;
console.log(debuggerCode);
form.validateFields((err, values) => {
if (!err) { // 表单验证通过时,调用测试接口
cb && cb(); // 调用回调函数,切换 tab
console.log('表单值:', values);
debuggerCode(values);
}
});
}
componentDidMount () {
const { testCases = []} = this.props;
this.setState({
status: testCases.length > 0 ? 'userCase' : 'default'
});
}
handleEditorDidMount = () => {}
render () { render () {
/** /**
* @param state 当前状态 default: 显示提示信息 init: 加载初始内容 loading: 加载中 loaded: 加载完成 final: 显示最终内容 * @param state 当前状态 default: 显示提示信息 init: 加载初始内容 loading: 加载中 loaded: 加载完成 final: 显示最终内容
@ -40,11 +99,29 @@ render () {
* @param testCase: 自定义测试用例 * @param testCase: 自定义测试用例
* @returns * @returns
*/ */
const { state, ctx, position = 'start', testCase} = this.props; const { testCodeStatus} = this.props;
const { ctx, position } = this.state;
// console.log('===>>>>> 测试用例集合: ', testCases);
return( return(
<React.Fragment> <React.Fragment>
{ maps[state](ctx, position) } { maps[testCodeStatus](ctx, position, this.props) }
</React.Fragment> </React.Fragment>
) )
} }
} }
const mapStateToProps = (state) => {
const ojFormReducer = state.ojFormReducer;
return {
testCases: ojFormReducer.testCases, // 测试用例
testCodeStatus: ojFormReducer.testCodeStatus
};
};
const mapDispatchToProps = (dispatch) => ({
debuggerCode: (value) => dispatch(actions.debuggerCode(value))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form.create()(InitTabCtx));

@ -13,6 +13,9 @@
&.pos_end{ &.pos_end{
justify-content: flex-end; justify-content: flex-end;
} }
.ctx_default{
margin: 10px 20px;
}
.ctx_loading, .ctx_loading,
.ctx_loaded{ .ctx_loaded{
display: flex; display: flex;
@ -26,3 +29,22 @@
} }
} }
} }
.user_case_form{
display: flex;
align-items: flex-start;
margin-top: 20px;
.input_area{
flex: 1;
.ant-form-item-required{
color: #fff;
}
}
.flex_l{
padding: 0 10px 0 20px;
color: #fff;
}
.flex_r{
padding: 0 20px 0 10px;
}
}

@ -0,0 +1,72 @@
/*
* @Description: 编辑器侧边栏设置信息
* @Author: tangjiang
* @Github:
* @Date: 2019-11-25 17:50:33
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 14:40:25
*/
import React from 'react';
import { Select } from 'antd';
const { Option } = Select;
const SettingDrawer = (props) => {
/**
* title: '', // 一级标题
* type: '', // 类型: 目录 select 和 文本
* content: [] // 显示的内容 { text: '' , value: string | [{ key: 1, value: '', text: '' }] }
*/
const {title, type = 'label', content = [] } = props;
const handleFontSize = (value) => {
const {onChangeFontSize} = props;
// console.log('fong size change: ', value);
onChangeFontSize && onChangeFontSize(value);
}
const renderCtx = (title, content = [], type = 'label') => {
const result = content.map((ctx, index) => {
const subText = ctx.text;
const value = ctx.value;
let renderResult = '';
if (typeof value === 'string') {
renderResult = (
<div className={'setting_desc'} key={`lab_${index}`}>
<span className={'flex_item'}>{subText}</span>
<span className={'flex_item'}>{ctx.value}</span>
</div>
);
} else if (Array.isArray(value)) {
if (type === 'select') {
const child = ctx.value.map((opt, i) => (
<Option key={opt.key || `${opt.value}`} value={opt.value}>
{opt.text}
</Option>
));
renderResult = (
<div className={'setting_desc'} key={`sel_${index}`}>
<span className={'flex_item'}>{ctx.text}</span>
<Select className={'flex_item'} style={{ width: '100px'}} onChange={handleFontSize}>
{child}
</Select>
</div>
);
}
}
return renderResult;
});
return (
<React.Fragment>
<h2 className={'setting_h2'}>{title}</h2>
{ result }
</React.Fragment>
);
}
return (
<div className={'setting_area'}>
{renderCtx(title, content, type)}
</div>
)
}
export default SettingDrawer;

@ -0,0 +1,183 @@
<!--
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-25 09:46:10
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-25 10:10:11
-->
## Quill配置
### 容器
- css 或者 DOM元素
```
const editor = new Quill(container)
```
### 配置项
var options = {
debug: 'info',
modules: {
toolbar: '#toolbar' // toolbar为一个代码块在页面中指定所需要的工具
},
placeholder: '', //
readOnly: false,
theme: 'snow'
}
const editor = new Quill('#editor', options);
- 对应的接口模型
```
export interface QuillOptionsStatic {
debug?: string | boolean;
modules: StringMap;
placeholder?: string;
readOnly?: boolean;
theme?: string;
formats?: string[];
bounds?: HTMLElement | string;
scrollingContainer?: HTMLElement | string;
strict?: boolean;
}
```
### 格式化
<br> Inline </br>
- background 背景色
- bold 粗体
- color 颜色
- font 字体
- code 内联代码
- italic 斜体
- link 链接
- size 大小
- strike 删除线
- script 上标/下标
- underline 下划线
<br> Block </br>
- blockquote 引用
- header 标题
- indent 缩进
- list 列表
- align 对齐方式
- direction 文字方向
- code-block 代码块
<br> Embeds </br>
- formula 公式 (需要 Katex)
- image 图片
- video 视频
### Quill 常用模块
- 工具栏
- 键盘
- 历史记录
- 剪贴板
- 语法高量
<b> 用法 </b>
> 工具栏模块 [toolbar](src="https://quilljs.com/docs/modules/toolbar/")
modules: {
toolbar: {
container: '#toolbar',
xx: {}
}
}
> 键盘模块 [keyboard](src="https://quilljs.com/docs/modules/keyboard/")
modules: {
keyboard: {
bindings: {
tab: {
key: 9,
handler: function () {}
}
}
}
}
> 历史模块
负责记录模块负责处理Quill的撤销和重做
modules: {
history: {
delay: 2000, // 在2000毫秒内的更改将被合并为单次更改
maxStack: 500, // 历史记录撤销/重做堆栈的大小
userOnly: true // 仅撤销或重做用户的更改
}
}
> 剪贴板模块
处理 Quill 和外部应用程序之间的复制
modules: {
clipboard: {
matchers: [
['B', xx]
]
}
}
> 语法高亮模块
语法高亮模块通过自动检测和应用语法突出显示来增强代码块格式。该模块依赖 [highlight.js](url="https://highlightjs.org/") 库用作解析和格式化代码块。
hljs.configure({ // optionally configure hljs
languages: ['javascript', 'ruby', 'python']
});
var quill = new Quill('#editor', {
modules: {
syntax: true, // Include syntax module
toolbar: [['code-block']] // Include button in toolbar
},
theme: 'snow'
});
> 模块扩展
Quill 中的模块可以被扩展和重新注册,从而替换原始模块
var Clipboard = Quill.import('modules/clipboard');
var Delta = Quill.import('delta');
class PlainClipboard extends Clipboard {
convert(html = null) {
if (typeof html === 'string') {
this.container.innerHTML = html;
}
let text = this.container.innerText;
this.container.innerHTML = '';
return new Delta().insert(text);
}
}
Quill.register('modules/clipboard', PlainClipboard, true);
// Will be created with instance of PlainClipboard
var quill = new Quill('#editor');

@ -0,0 +1,135 @@
/*
* @Description: Quill 编辑器
* @Author: tangjiang
* @Github:
* @Date: 2019-11-25 09:46:03
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 19:28:50
*/
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.bubble.css';
import 'quill/dist/quill.snow.css';
import 'katex/dist/katex.css';
import './index.scss';
import React from 'react';
import katex from 'katex';
const Quill = require('quill');
// 将katex挂载到window上
window.katex = katex;
window.Quill = Quill;
// 指定 Quill 默认配置项
const defaultOptions = [
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 自定义标题大小
['bold', 'italic', 'underline', 'strike'], // 切换按钮
['blockquote', 'code-block'], // 代码块
[{ 'list': 'ordered' }, { 'list': 'bullet' }], // 列表
[{ 'script': 'sub'}, { 'script': 'super' }], // 上标/下标
[{ 'indent': '-1'}, { 'indent': '+1' }], // 减少缩进/缩进
[{ 'direction': 'rtl' }],
[{ 'size': ['small', 'large', 'huge', false] }], // 用户自定义下拉
[{ 'color': [] }, { 'background': [] }], // 字体颜色与背景色
[{ 'font': [] }, { 'align': [] }], // 字体与对齐方式
['formula', 'image', 'video'], // 数学公式、图片、视频
['clean'], // 清除格式
];
/**
* @description 抽取一个React编辑器组件基于Quill
* @class QuillEditor类
* @param [object] props 接收的属性
*
* props: {
* options: {} // 编辑器配置信息, 不传使用 defaultOptions, 传了的话 使用用户自定义的,
* placeholder: '' // 编辑器提示信息
* innerHtml: '', // 编辑器内容
* onEditorChange: '', // 编辑器内容改变时调用此方法, 返回更改的内容
* }
* @return [stirng] content 返回编辑器内容
*/
class QuillEditor extends React.Component {
state = {
quillEditor: null,
// quillOptions: defaultOptions
}
constructor (props) {
super(props);
this.editorRef = React.createRef(null);
}
componentDidMount () {
const { options, placeholder = '', readOnly = false } = this.props;
let { quillEditor } = this.state;
const renderOptions = options || defaultOptions;
const editorOption = {
placeholder: placeholder,
modules: {
toolbar: renderOptions
},
readOnly,
theme: 'snow',
}
// 实例化 Quill 编辑器
quillEditor = new Quill(this.editorRef.current, editorOption);
this.setState({
quillEditor: quillEditor
});
// 开启一个定时器读取 html初始时, 如果没有最多执行10次后自动清
let count = 0;
this.timer = setInterval(() => {
count++;
if (count >= 10 || this.props.htmlCtx) {
quillEditor.container.firstChild.innerHTML = this.props.htmlCtx;
clearInterval(this.timer);
this.timer = null;
}
// console.log('定时器====>>>>>', count);
}, 50);
// quillEditor.setText('<p>aaa</p>');
quillEditor.on('editor-change', this.handleQuillChange);
// console.log('====>>>', quillEditor);
}
// 处理quill事件 editor-change
/**
* @param [string] eventName 事件名
* @param [object] args 参数
*/
handleQuillChange = (eventName, ...args) => {
const { onEditorChange } = this.props;
// 获取编辑器内容
const innerHTML = this.state.quillEditor.container.firstChild.innerHTML;
onEditorChange && onEditorChange(innerHTML);
// if ('text-change' === eventName) {
// const {delta, oldDelta, source} = args;
// console.log('textChange', delta, oldDelta, source);
// } else if ('selection-change' === eventName) {
// const {range, oldRange, source} = args;
// console.log('selectionChange', range, oldRange, source);
// }
}
componentWillUnmount () {
// 删除事件监听
this.state.quillEditor.off(this.handleQuillChange);
}
render () {
const styles = this.props.style || {}
return (
<div
id="quill_editor"
style={styles}
className={'quill_editor_area'}
ref={this.editorRef}>
</div>
);
}
}
export default QuillEditor;

@ -0,0 +1,3 @@
.quill_editor_area{
height: 300px;
}

@ -26,7 +26,8 @@
} }
} }
.split-pane-area{ .split-pane-area,
.pane_right_area{
position: relative; position: relative;
height: calc(100% - 65px); height: calc(100% - 65px);
.left_pane, .left_pane,
@ -67,6 +68,32 @@
} }
} }
.split-pane-area,
.split-pane-left{
.ant-tabs-nav-wrap{
padding: 0 30px;
}
.ant-tabs-bar{
margin: 0;
}
// .ant-tabs-tabpane{
// padding-top: 10px;
// height: calc(100vh - 110px);
// overflow: auto;
// }
.ant-form-item-control{
line-height: 1;
}
.editor_area,
.prev_area{
height: calc(100vh - 110px);
overflow-y: auto;
// padding: 20px 0;
}
}
.Resizer { .Resizer {
background: #000; background: #000;
opacity: 0.2; opacity: 0.2;

@ -4,18 +4,26 @@
* @Github: * @Github:
* @Date: 2019-11-23 10:53:19 * @Date: 2019-11-23 10:53:19
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-23 11:48:10 * @LastEditTime: 2019-11-28 16:04:10
*/ */
import './index.scss'; import './index.scss';
import React from 'react'; import React, { useEffect } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import SplitPane from 'react-split-pane'; import SplitPane from 'react-split-pane';
import LeftPane from './leftpane'; import LeftPane from './leftpane';
import RightPane from './rightpane'; import RightPane from './rightpane';
import { Link } from 'react-router-dom';
// import RightPane from '../newOrEditTask/rightpane';
import { Button, Avatar } from 'antd'; import { Button, Avatar } from 'antd';
import actions from '../../../redux/actions';
const StudentStudy = (props) => { const StudentStudy = (props) => {
useEffect(() => {
const { match: { params }, startProgramQuestion } = props;
let { id } = params;
startProgramQuestion(id);
}, []);
return ( return (
<div className={'student_study_warp'}> <div className={'student_study_warp'}>
<div className={'student_study_header'}> <div className={'student_study_header'}>
@ -29,7 +37,9 @@ const StudentStudy = (props) => {
<span>乘积最大序列</span> <span>乘积最大序列</span>
</div> </div>
<div className={'study_quit'}> <div className={'study_quit'}>
<Button>退出</Button> <Button>
<Link to="/developer">退出</Link>
</Button>
</div> </div>
</div> </div>
<div className="split-pane-area"> <div className="split-pane-area">
@ -49,7 +59,10 @@ const StudentStudy = (props) => {
const mapStateToProps = (state) => ({}); const mapStateToProps = (state) => ({});
const mapDispatchToProps = (dispatch) => ({}); const mapDispatchToProps = (dispatch) => ({
// 调用开启编辑
startProgramQuestion: (id) => dispatch(actions.startProgramQuestion(id))
});
export default connect( export default connect(
mapStateToProps, mapStateToProps,

@ -1 +1,7 @@
@import '../split_pane_resizer.scss'; @import '../split_pane_resizer.scss';
.right_pane_code_wrap{
position: relative;
background-color: #222;
height: 100%;
}

@ -0,0 +1,18 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 09:49:35
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 09:52:53
*/
import React from 'react';
const Comment = (props) => {
return (
<h2> Comment </h2>
)
}
export default Comment;

@ -0,0 +1,165 @@
/*
* @Description: 提交记录
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 09:49:33
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 19:54:56
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
import { Table, Icon } from 'antd';
import { connect } from 'react-redux';
import actions from '../../../../../redux/actions';
import CONST from '../../../../../constants';
import moment from 'moment';
const numberal = require('numeral');
const {reviewResult} = CONST;
const columns = [
{
title: '提交时间',
dataIndex: 'created_at',
render: (created_at) => (
<span>
{moment(created_at, 'YYYYMMDD HHmmss').fromNow()}
</span>)
},
{
title: '提交结果',
dataIndex: 'status',
render: (value) => (<span style={{ color: value === 0 ? '#28BD8B' : '#E6262E'}}>{reviewResult[value]}</span>)
},
{
title: '执行用时',
dataIndex: 'execute_time',
render: (value) => (<span>{`${value}s`}</span>)
},
{
title: '内存消耗',
dataIndex: 'execute_memory',
render: (value) => {
if (value) {
return <span>{numberal(+value).format('0.00b')}</span>
} else {
return (<span>0MB</span>)
}
}
},
{
title: '语言',
dataIndex: 'language'
}
]
const paginationConfig = {
total: 1, // 总条数
pageSize: 10, // 每页显示条数
current: 1, // 当前页数
showQuickJumper: true
}
const CommitRecord = (props) => {
const {
identifier,
commitRecord,
commitRecordDetail,
getUserCommitRecord
} = props;
const [pagination, setPagination] = useState(paginationConfig);
const [tableData, setTableData] = useState([]);
// const [recordDetail, setRecordDetail] = useState({});
const [renderCtx, setRenderCtx] = useState(() => {
return function () {
return '';
}
});
// 渲染提交记录详情
const renderRecordDetail = () => {
const {
error_line,
error_msg,
execute_memory,
execute_time,
input,
output,
status,
expected_output
} = commitRecordDetail;
console.log('========', commitRecordDetail);
if (Object.keys(commitRecordDetail).length > 0) {
const classes = status === 0 ? 'record_result_suc' : 'record_result_err';
return (
<React.Fragment>
<div className={'record_header'}>
<span className={'record_result'}>
执行结果: <span className={classes}>{reviewResult[status]}</span>
</span>
<span className={'copy_error'}>
复制错误信息 <Icon type="copy" className={'icon_style'}/>
</span>
<span className={'show_detail'}>
显示详情 <Icon type="right" className={'icon_style'}/>
</span>
</div>
{/* <div className={'record_error_info'}>错误代码</div> */}
</React.Fragment>
);
} else {
return '';
}
}
useEffect(() => {
// console.log('调用记录详情=====>>>');
getUserCommitRecord(identifier);
}, []);
useEffect(() => {
// console.log('====>>>>+++++++++++++', commitRecord);
setTableData(commitRecord);
}, [commitRecord]);
useEffect(() => {
// setRecordDetail(commitRecordDetail);
setRenderCtx(() => (renderRecordDetail))
}, [commitRecordDetail]);
console.log(commitRecord);
return (
<div className={'commit_record_area'}>
{renderCtx()}
<Table
columns={columns}
rowKey={record => Math.random()}
dataSource={tableData}
pagination={pagination}
/>
</div>
)
}
const mapStateToProps = (state) => {
const {
ojForUserReducer
} = state;
const {
user_program_identifier,
commitRecordDetail,
commitRecord
} = ojForUserReducer;
return {
identifier: user_program_identifier,
commitRecordDetail,
commitRecord // 提交记录
}
}
const mapDispatchToProps = (dispatch) => ({
getUserCommitRecord: (identifier) => dispatch(actions.getUserCommitRecord(identifier))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(CommitRecord);

@ -0,0 +1,41 @@
.commit_record_area{
padding: 20px 30px;
.record_header{
display: flex;
// justify-content: space-between;
// background: gold;
height: 66px;
align-items: center;
.record_result{
color: #333333;
font-size: 16px;
// width:1px;
}
.copy_error{
text-align: right;
flex: 1;
padding-right: 40px;
}
.show_detail{
// width: 1px;
}
.copy_error,
.show_detail{
color: #5091FF;
font-size: 14px;
}
.icon_style{
font-size: 12px;
margin-left: 2px;
}
.record_result_suc{
color: #28BD8B;
}
.record_result_err{
color: #E51C24;
}
}
.record_error_info{
padding: 20px 30px;
}
}

@ -1,18 +1,102 @@
/* /*
* @Description: * @Description: 学员测评页面
* @Author: tangjiang * @Author: tangjiang
* @Github: * @Github:
* @Date: 2019-11-23 11:33:41 * @Date: 2019-11-23 11:33:41
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-23 11:34:18 * @LastEditTime: 2019-11-28 16:12:58
*/ // */
import React from 'react'; import './index.scss';
import React, { useState, useEffect } from 'react';
import { Tabs, Divider } from 'antd';
import { connect } from 'react-redux';
import Comment from './comment';
import CommitRecord from './commitRecord';
import TaskDescription from './taskDescription';
import TextNumber from './../../components/textNumber';
import actions from '../../../../redux/actions';
const { TabPane } = Tabs;
const LeftPane = (props) => { const LeftPane = (props) => {
const { hack, userCodeTab, changeUserCodeTab } = props;
const { pass_count, submit_count } = hack;
const [defaultActiveKey, setDefaultActiveKey] = useState('task');
console.log(pass_count, submit_count);
const tabArrs = [
{ title: '任务描述', key: 'task', content: (<TaskDescription />) },
{ title: '提交记录', key: 'record', content: (<CommitRecord />) },
// { title: '评论', key: 'comment', content: (<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 handleClickMessage = () => {
console.log('点击的消息图标---------');
}
// 点击点赞
const handleClickLike = () => {
console.log('点击的Like---------');
}
// 点击不喜欢
const handleClickDisLike = () => {
console.log('点击的DisLike---------');
}
return ( return (
<h2>左侧内容</h2> <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>
</React.Fragment>
); );
} }
export default LeftPane; const mapStateToProps = (state) => {
const { hack, userCodeTab} = state.ojForUserReducer;
return {
hack,
userCodeTab
}
}
// changeUserCodeTab
const mapDispatchToProps = (dispatch) => ({
changeUserCodeTab: (key) => dispatch(actions.changeUserCodeTab(key))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(LeftPane);

@ -0,0 +1,51 @@
@import '../../split_pane_resizer.scss';
.user_code_tab_area{
.ant-tabs-tabpane{
background: #fff;
}
}
.number_area{
display: flex;
position: absolute;
align-items: center;
justify-content: space-between;
bottom: 0px;
height: 56px;
width: 100%;
// background: pink;
padding: 0 30px;
// background-color: #fff;
.flex_count,
.flex_info{
display: flex;
flex-direction: row;
justify-content: space-between;
}
.flex_info{
width: 200px;
}
}
.task_description_area{
padding: 0 30px;
height: calc(100vh - 166px);
overflow-y: auto;
.desc_area_header{
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;
.header_flex{
font-size: 14px;
.flex_label{
color: #999999;
margin-right: 10px;
}
.flex_value{
font-weight: bold;
}
}
}
}

@ -0,0 +1,62 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 09:49:30
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-27 19:36:41
*/
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 CONST from '../../../../../constants';
const {tagBackground, diffText} = CONST;
const TaskDescription = (props) => {
const { hack = {} } = props;
const {language, difficult, time_limit, username, description} = hack;
return (
<div className={'task_description_area'}>
<div className={'desc_area_header'}>
<p className={'header_flex'}>
<span className={'flex_label'}>编程语言:</span>
<span className={'flex_value'}>{language}</span>
</p>
<p className={'header_flex'}>
<span className={'flex_label'}>难度:</span>
<Tag color={tagBackground[+difficult]}>{diffText[+difficult]}</Tag>
</p>
<p className={'header_flex'}>
<span className={'flex_label'}>程序运行时间限制:</span>
<span className={'flex_value'}>{time_limit}</span>
</p>
<p className={'header_flex'}>
<span className={'flex_label'}>出题者:</span>
<Link to="/" style={{ color: '#5091FF'}}>{username}</Link>
</p>
</div>
<QuillEditor
htmlCtx={description}
readOnly={true}
options={[]}
style={{ height: "calc(100% - 109px)" }}
/>
{/* <div dangerouslySetInnerHTML={{__html: description}}></div> */}
</div>
)
}
const mapStateToProps = (state) => {
const { hack } = state.ojForUserReducer;
return {
hack
}
}
export default connect(
mapStateToProps
)(TaskDescription);

@ -2,17 +2,54 @@
* @Description: * @Description:
* @Author: tangjiang * @Author: tangjiang
* @Github: * @Github:
* @Date: 2019-11-23 11:34:32 * @Date: 2019-11-27 14:59:51
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-23 11:34:48 * @LastEditTime: 2019-11-28 17:17:25
*/ */
import React from 'react'; import React from 'react';
import {connect} from 'react-redux';
import MyMonacoEditor from '../../components/myMonacoEditor';
import ControlSetting from '../../components/controlSetting';
import actions from '../../../../redux/actions';
const RightPane = (props) => { const RightPane = (props) => {
const {identifier, submitInput, submitUserCode} = props;
const handleSubmitForm = () => {
console.log('提交了表单内容');
// 提交时, 先调用提交接口,提交成功后,循环调用测评接口
submitUserCode(identifier, submitInput, 'submit');
// // 提交时,先调用评测接口, 评测通过后才调用保存接口
// updateCode(identifier, submitInput, 'submit');
}
return ( return (
<h2>右侧内容</h2> <div className={'right_pane_code_wrap'}>
<MyMonacoEditor language={props.language} code={props.code}/>
<ControlSetting inputValue={props.input} onSubmitForm={handleSubmitForm}/>
</div>
); );
} }
export default RightPane; const mapStateToProps = (state) => {
const {user_program_identifier, hack, userTestInput} = state.ojForUserReducer;
const { language, code } = hack;
return {
language,
code,
input: userTestInput,
submitInput: hack.input,
identifier: user_program_identifier
};
}
const mapDispatchToProps = (dispatch) => ({
// type: 提交类型 debug | submit
// updateCode: (identifier, inputValue, type) => dispatch(actions.updateCode(identifier, inputValue, type))
submitUserCode: (identifier, inputValue, type) => dispatch(actions.submitUserCode(identifier, inputValue, type))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(RightPane);

@ -806,12 +806,6 @@ submittojoinclass=(value)=>{
} }
}else { }else {
const developer = {
name: '开发者社区',
link: '/developer',
hidden: false
};
mygetHelmetapi2.navbar.push(developer);
for(var i=0;i<mygetHelmetapi2.navbar.length;i++){ for(var i=0;i<mygetHelmetapi2.navbar.length;i++){
if(match.path===mygetHelmetapi2.navbar[i].link){ if(match.path===mygetHelmetapi2.navbar[i].link){
headtypes=mygetHelmetapi2.navbar[i].link; headtypes=mygetHelmetapi2.navbar[i].link;

@ -24,6 +24,24 @@ const types = {
DELETE_TEST_CASE: 'DELETE_TEST_CASE', // 删除测试用例 DELETE_TEST_CASE: 'DELETE_TEST_CASE', // 删除测试用例
SAVE_TEST_CASE: '保存测试用例', // 保存测试用例 SAVE_TEST_CASE: '保存测试用例', // 保存测试用例
CLEAR_JSFORM_STORE: 'CLEAR_JSFORM_STORE', // 清空测试用例 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', // 代码调试状态
VALIDATE_TEST_CODE_ARRS: 'VALIDATE_TEST_CODE_ARRS', // 更改测试用例验证结果
TEST_CASE_INPUT_CHANGE: 'TEST_CASE_INPUT_CHANGE', // 测试用例输入值改变时
TEST_CASE_OUTPUT_CHANGE: 'TEST_CASE_OUTPUT_CHANGE', // 测试用例输出值改变时
DEBUGGER_CODE: 'DEBUGGER_CODE', // 调试代码
SAVE_USER_PROGRAM_ID: 'SAVE_USER_PROGRAM_ID',// 保存用户编程题id值
USER_PROGRAM_DETAIL: 'USER_PROGRAM_DETAIL', // 用户编程题详情
SHOW_OR_HIDE_CONTROL: 'SHOW_OR_HIDE_CONTROL', // 显示或隐藏控制台
LOADING_STATUS: 'LOADING_STATUS', // loading状态
COMMIT_RECORD_DETAIL: 'COMMIT_RECORD_DETAIL', // 提交记录详情
COMMIT_RECORD: 'COMMIT_RECORD', // 提交记录
SAVE_USER_CODE: 'SAVE_USER_CODE', // 用户编辑的代码块
IS_UPDATE_CODE: 'IS_UPDATE_CODE', // 是否更新代码块内容
CHANGE_USER_CODE_TAB: 'CHANGE_USER_CODE_TAB', // 切换学员测评tab
SUBMIT_LOADING_STATUS: 'SUBMIT_LOADING_STATUS', // 提交按钮状态值
PUBLISH_LOADING_STATUS: 'PUBLISH_LOADING_STATUS', // 发布按钮
IS_MY_SOURCE: 'IS_MY_SOURCE'
} }
export default types; export default types;

@ -0,0 +1,49 @@
/*
* @Description: 控制全局
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 16:30:50
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 21:15:34
*/
import types from "./actionTypes";
// 切换控制台显示与隐藏
export const showOrHideControl = (flag) => {
return {
type: types.SHOW_OR_HIDE_CONTROL,
payload: flag
}
}
// 改变 loading 状态值
export const changeLoadingState = (flag) => {
return {
type: types.LOADING_STATUS,
payload: flag
}
}
// 改变提交按钮状态值
export const changeSubmitLoadingStatus = (flag) => {
return {
type: types.SUBMIT_LOADING_STATUS,
payload: flag
}
}
// 发布按钮状态
export const changePublishLoadingStatus = (flag) => {
return {
type: types.PUBLISH_LOADING_STATUS,
payload: flag
}
}
// 是否是我发布的
export const isMyPublish = (flag) => {
return {
type: types.IS_MY_SOURCE,
payload: flag
}
}

@ -22,9 +22,31 @@ import {
validateOjCategory, validateOjCategory,
validateOpenOrNot, validateOpenOrNot,
addTestCase, addTestCase,
deleteTestCase deleteTestCase,
testCaseInputChange,
testCaseOutputChange,
} from './ojForm'; } from './ojForm';
import {
startProgramQuestion,
debuggerCode,
getUserCommitRecord,
getUserCommitRecordDetail,
updateCode,
saveUserInputCode,
changeUserCodeTab,
submitUserCode,
// isUpdateCodeCtx
} from './ojForUser';
import {
showOrHideControl,
changeLoadingState,
changeSubmitLoadingStatus,
changePublishLoadingStatus,
isMyPublish,
} from './common';
export default { export default {
toggleTodo, toggleTodo,
getOJList, getOJList,
@ -41,5 +63,21 @@ export default {
validateOjCategory, validateOjCategory,
validateOpenOrNot, validateOpenOrNot,
addTestCase, addTestCase,
deleteTestCase deleteTestCase,
testCaseInputChange,
testCaseOutputChange,
debuggerCode,
startProgramQuestion,
showOrHideControl,
changeLoadingState,
getUserCommitRecord,
getUserCommitRecordDetail,
updateCode,
saveUserInputCode,
changeUserCodeTab,
changeSubmitLoadingStatus,
submitUserCode,
changePublishLoadingStatus,
isMyPublish
// isUpdateCodeCtx
} }

@ -0,0 +1,235 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 13:42:11
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 17:32:52
*/
import types from "./actionTypes";
import { Base64 } from 'js-base64';
import {
fetchStartProgram,
fetchUserProgramDetail,
fetchDebuggerCode, fetchCodeSubmit,
fetchUserCommitRecord,
fetchUserCommitRecordDetail,
fetchUpdateCode,
fetchUserCodeSubmit
} from "../../services/ojService";
// 进入编程页面时,首先调用开启编程题接口
export const startProgramQuestion = (id) => {
return (dispatch) => {
fetchStartProgram(id).then(res => {
console.log('调用编程题成功返回的数据: ', res);
const { status, data } = res;
if (status === 200) {
const {identifier} = data;
dispatch({
type: types.SAVE_USER_PROGRAM_ID,
payload: identifier
});
// 调用用户编程详情接口
fetchUserProgramDetail(identifier).then(res => {
console.log('用户编程题详情: =====>>>>>', res);
const { status, data = {} } = res;
if (status === 200) {
dispatch({
type: types.USER_PROGRAM_DETAIL,
payload: data
});
}
})
}
})
}
}
/**
* @description 更新代码
* @param {*} identifier
* @param {*} inputValue 输入值: 自定义 | 系统返回的
* @param {*} type 测评类型 debug | submit
*/
export const updateCode = (identifier, inputValue, type) => {
return (dispatch, getState) => {
const { userCode, isUpdateCode } = getState().ojForUserReducer;
// console.log(userCode, isUpdateCode);
isUpdateCode && fetchUpdateCode(identifier, {
code: userCode
}).then(res => {
// 是否更新了代码, 目的是当代码没有更新时不调用更新代码接口,目录没有实现
// TODO 需要优化
dispatch({
type: types.IS_UPDATE_CODE,
flag: false
});
// debuggerCode(identifier, inputValue);
dispatch(debuggerCode(identifier, inputValue, type));
});
}
}
/**
* @description 调试代码
* @param {*} identifier
* @param {*} inputValue 输入值: 自定义 | 系统返回的
* @param {*} type 测评类型 debug | submit
*/
export const debuggerCode = (identifier,value, type) => {
return (dispatch, getState) => {
// 调用之前 先保存 code
// TODO
// console.log(identifier, value);
const {hack: {time_limit = 0}} = getState().ojForUserReducer;
if (!type || type === 'debug') {
dispatch({ // 加载中...
type: types.TEST_CODE_STATUS,
payload: 'loading'
});
}
fetchDebuggerCode(identifier, value).then(res => {
console.log('调用调试代码成功并返回结果: ', res);
const { status } = res;
if (status === 200) {
// 调试代码成功后,调用轮循接口, 注意: 代码执行的时间要小于设置的时间限制
const intervalTime = 500;
let count = 1;
/**
* @param {*} excuteTime 执行时间
* @param {*} finalTime 总时间
* @param {*} count 执行次数
* @param {*} timer 定时器
*/
function getCodeSubmit (intervalTime, finalTime, count, timer){
const excuteTime = (count++) * intervalTime; // 当前执行时间
// console.log(count);
fetchCodeSubmit(identifier, { mode: type }).then(res => {
const { data } = res;
const { status } = data;
// 清除定时器条件: 评测通过或者评测时间大于指定时间
if (+status === 0 || (excuteTime / 1000) > (finalTime + 1)) {
clearInterval(timer); //
timer = null;
const { error_msg }= data.data;
console.log('后台返回错误信息======++++', Base64.decode(error_msg));
const returnData = data.data;
if (!type || type === 'debug') {
dispatch({ // 加载完成
type: types.TEST_CODE_STATUS,
payload: 'loaded'
});
dispatch({ // 改变 loading 值
type: types.LOADING_STATUS,
payload: false
});
// 加载状态变成finish
dispatch({ // 加载完成
type: types.TEST_CODE_STATUS,
payload: 'finish'
});
}
dispatch({
type: types.COMMIT_RECORD_DETAIL,
payload: returnData
});
// 改变tab值至 record
dispatch({
type: types.CHANGE_USER_CODE_TAB,
payload: 'record'
});
// 重新调用一下提交记录接口
dispatch(getUserCommitRecord(identifier));
}
}).catch(err => {
clearInterval(timer);
timer = null;
});
}
let timer = setInterval(() => {
getCodeSubmit(intervalTime, time_limit, count++, timer);
}, intervalTime);
}
});
}
}
// 获取提交记录
export const getUserCommitRecord = (identifier) => {
return (dispatch) => {
fetchUserCommitRecord(identifier).then(res => {
console.log('提交记录======>>>', res);
const {status, data} = res;
if (status === 200) {
dispatch({
type: types.COMMIT_RECORD,
payload: data
})
}
});
}
}
// 获取提交记录详情
export const getUserCommitRecordDetail = () => {
return (dispatch) => {
fetchUserCommitRecordDetail().then(res => {
console.log('提交记录详情======》》》》', res);
});
}
}
// 保存用户时时输入的代码
export const saveUserInputCode = (code) => {
return {
type: types.SAVE_USER_CODE,
payload: code
}
}
// 监听是否更新代码块内容
// export const isUpdateCodeCtx = (flag) => {
// return {
// type: types.IS_UPDATE_CODE,
// payload: flag
// };
// }
// 改变学员测评 tab 值
export const changeUserCodeTab = (key) => {
return {
type: types.CHANGE_USER_CODE_TAB,
payload: key
}
}
/**
* @description 用户提交代码 先调用保存代码接口再调提交接口成功后调用调试接口
* @param {*} identifier
*/
export const submitUserCode = (identifier, inputValue, type) => {
return (dispatch, getState) => {
const { userCode, isUpdateCode } = getState().ojForUserReducer;
isUpdateCode && fetchUpdateCode(identifier, {
code: userCode
}).then(res => {
// 是否更新了代码, 目的是当代码没有更新时不调用更新代码接口,目录没有实现
// TODO 需要优化
dispatch({
type: types.IS_UPDATE_CODE,
flag: false
});
fetchUserCodeSubmit(identifier).then(res => {
console.log('用户提交代码成功======》》》》》', res);
if (res.status === 200) {
dispatch(debuggerCode(identifier, inputValue, type || 'submit'));
}
})
});
}
}

@ -4,12 +4,14 @@
* @Github: * @Github:
* @Date: 2019-11-20 16:35:46 * @Date: 2019-11-20 16:35:46
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 20:07:04 * @LastEditTime: 2019-11-28 21:21:56
*/ */
import types from './actionTypes'; import types from './actionTypes';
import jcLabel from '../../constants'; import CONST from '../../constants';
import { fetchPostOjForm, fetchGetOjById } from '../../services/ojService'; import { fetchPostOjForm, fetchGetOjById, publishTask } from '../../services/ojService';
import { Base64 } from 'js-base64';
import { message, notification } from 'antd';
const { jcLabel } = CONST;
// 表单字段映射 // 表单字段映射
const maps = { const maps = {
name: { name: {
@ -39,6 +41,12 @@ const maps = {
openOrNot: { openOrNot: {
label: jcLabel['openOrNot'], label: jcLabel['openOrNot'],
type: types.VALIDATE_OJ_OPENORNOT type: types.VALIDATE_OJ_OPENORNOT
},
input: {
label: '输入'
},
output: {
label: '输出'
} }
}; };
@ -72,9 +80,9 @@ const payloadInfo = (key, value, errMsg, validateInfo) => ({
}); });
// 表单提交验证 // 表单提交验证
export const validateOjForm = () => { export const validateOjForm = (props, type) => {
return (dispatch, getState) => { return (dispatch, getState) => {
const {ojForm} = getState().ojFormReducer; const {ojForm, testCases, identifier, code } = getState().ojFormReducer;
let keys = Object.keys(ojForm); let keys = Object.keys(ojForm);
// 循环判断每个字段是否为空 // 循环判断每个字段是否为空
let hasSuccess = true; let hasSuccess = true;
@ -92,14 +100,52 @@ export const validateOjForm = () => {
) )
} }
}); });
// 验证测试用例中的数组是否都有对应的值
const tcValidResult = [];
testCases.forEach(obj => {
// const tcKeys = Object.keys(obj); // 获取 obj 属性: input 与 output
let tempObj = {};
['input', 'output'].forEach(key => {
const value = obj[key];
const validateResult = emptyValidate(key, value);
const errMsg = validateResult[key].errMsg;
// const errMsg = validateResult[key].errMsg;
if (errMsg) {
hasSuccess = false;
}
Object.assign(tempObj, validateResult);
});
tcValidResult.push(tempObj);
});
if (testCases.length === 0) {
notification['error']({
message: '必填',
description: '测试用例必须输入!'
});
return false;
}
if (!code) {
notification['error']({
message: '必填',
description: '代码块内容必须输入!'
});
return;
}
// 更改测试用例验证结果
dispatch({
type: types.VALIDATE_TEST_CODE_ARRS,
payload: tcValidResult
});
// 验证成功后,调用提交接口 // 验证成功后,调用提交接口
if (hasSuccess) { if (hasSuccess) {
console.log('表单保存的数据为: ', getState()); // console.log('表单保存的数据为: ', getState());
const {ojFormReducer} = getState(); const {ojFormReducer} = getState();
const {code, score, ojForm, testCases} = ojFormReducer; const {code, score, ojForm, testCases = []} = ojFormReducer;
const {category, description, difficult, language, name, openOrNot, timeLimit} = ojForm; const {category, description, difficult, language, name, openOrNot, timeLimit} = ojForm;
const params = { let paramsObj = {};
hack: { const hack = { // 编程题干
name, name,
description, description,
difficult, difficult,
@ -107,19 +153,97 @@ export const validateOjForm = () => {
'open_or_not': openOrNot, 'open_or_not': openOrNot,
'time_limit': timeLimit, 'time_limit': timeLimit,
score score
}, };
'hack_sets': testCases,
'hack_codes': { const hack_codes = { // 代码区域参数
code, code: Base64.encode(code),
language language
};
// const tempTc = testCases.map(tc => {
// delete tc.isAdd
// return tc;
// });
// console.log(tempTc);
if (!identifier) { // 新增
const tempTc = testCases.map(tc => {
delete tc.isAdd
return tc;
});
paramsObj['params'] = {
hack,
hack_sets: tempTc,
hack_codes
} }
paramsObj['submitType'] = 'add';
} else { // 存在时调用更新接口
const update_hack_sets = []; // 编辑的测试集
const hack_sets = []; // 新增的测试集
testCases.forEach(tc => {
if (tc.isAdd) { // 新增
delete tc.isAdd;
hack_sets.push(tc);
} else {
delete tc.isAdd;
update_hack_sets.push(tc);
} }
fetchPostOjForm(params).then(res => { });
paramsObj['params'] = {
hack,
hack_sets,
hack_codes,
update_hack_sets
}
paramsObj['submitType'] = 'update';
paramsObj['identifier'] = identifier;
}
function linkToDev () {
dispatch({
type: types.IS_MY_SOURCE,
payload: true
});
setTimeout(() => {
props.history.push('/developer');
}, 1000);
}
fetchPostOjForm(paramsObj).then(res => {
// TODO // TODO
if (res.status === 200) { // 保存成功后,重新跳转至列表页
const {identifier} = res.data
if (type === 'publish') { // 存在发布时,直接调用发布接口
identifier && publishTask(identifier).then(res => {
if (res.status === 200) {
message.success('发布成功!');
linkToDev();
}
dispatch({
type: types.PUBLISH_LOADING_STATUS,
payload: false
});
}).catch(() => {
dispatch({
type: types.PUBLISH_LOADING_STATUS,
payload: false
});
});
} else {
message.success('保存成功!');
linkToDev();
dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
});
}
}
}).catch(err => { }).catch(err => {
// TODO dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
});
}); });
// dispatch({type: types.VALIDATE_OJ_FORM});
} }
} }
}; };
@ -211,12 +335,16 @@ export const deleteTestCase = (obj) => {
payload: obj payload: obj
} }
} }
// 更新id号编辑OJ // 根据id号编辑OJ
export const getOJFormById = (id) => { export const getOJFormById = (id) => {
return (dispatch) => { return (dispatch) => {
fetchGetOjById(id).then(res => { fetchGetOjById(id).then(res => {
console.log('获取OJ表单信息成功: ', res); console.log('获取OJ表单信息成功: ', res);
}) dispatch({
type: types.SAVE_EDIT_OJ_FORM_AND_TEST_CASE,
payload: res.data
});
});
} }
} }
// 保存表单 id 信息 // 保存表单 id 信息
@ -232,3 +360,32 @@ export const clearOJFormStore = () => {
type: types.CLEAR_JSFORM_STORE type: types.CLEAR_JSFORM_STORE
} }
} }
// 测试用例输入值改变时
export const testCaseInputChange = (value, index) => {
const validate = emptyValidate('input', value)['input'];
return {
type: types.TEST_CASE_INPUT_CHANGE,
payload: {
input: validate,
value,
index
}
}
}
// 测试用例输出值改变时
export const testCaseOutputChange = (value, index) => {
const validate = emptyValidate('output', value)['output'];
return {
type: types.TEST_CASE_OUTPUT_CHANGE,
payload: {
output: validate,
value,
index
}
}
}
// // 调试代码时,更改对应的状态值
// export const changeTestCodeStatus = () => {

@ -0,0 +1,58 @@
/*
* @Description: 全局控制 reducer
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 16:27:09
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 21:14:36
*/
import types from "../actions/actionTypes";
const initialState = {
showOrHideControl: false,
loading: false,
excuteState: '', // 代码执行状态
submitLoading: false, // 提交按钮状态
publishLoading: false, // 发布
isMySource: false
}
const commonReducer = (state = initialState, action) => {
switch (action.type) {
case types.SHOW_OR_HIDE_CONTROL:
return {
...state,
showOrHideControl: action.payload
}
case types.LOADING_STATUS:
return {
...state,
loading: action.payload
}
case types.TEST_CODE_STATUS: // 改变代码调试状态
return {
...state,
excuteState: action.payload
}
case types.SUBMIT_LOADING_STATUS:
return {
...state,
submitLoading: action.payload
}
case types.PUBLISH_LOADING_STATUS:
return {
...state,
publishLoading: action.payload
}
case types.IS_MY_SOURCE:
return {
...state,
isMySource: action.payload
}
default:
return state;
}
}
export default commonReducer;

@ -6,15 +6,16 @@
* @Last Modified time: 2019-11-14 09:55:10 * @Last Modified time: 2019-11-14 09:55:10
*/ */
import { import { combineReducers } from 'redux';
combineReducers
} from 'redux';
import testReducer from './testReducer'; import testReducer from './testReducer';
import ojFormReducer from './ojFormReducer'; import ojFormReducer from './ojFormReducer';
import ojListReducer from './ojListReducer'; import ojListReducer from './ojListReducer';
import ojForUserReducer from './ojForUserReducer';
import commonReducer from './commonReducer';
export default combineReducers({ export default combineReducers({
testReducer, testReducer,
ojFormReducer, ojFormReducer,
ojListReducer ojListReducer,
ojForUserReducer,
commonReducer
}); });

@ -0,0 +1,77 @@
/*
* @Description: 用户编程信息
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 13:41:48
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-28 17:34:13
*/
import types from "../actions/actionTypes";
import { Base64 } from 'js-base64';
const initialState = {
user_program_identifier: '', // 开启OJ题的唯一标题
hack: {}, // 编程题主要内容
test_case: {}, // 测试用例
commitRecordDetail: {}, // 提交记录详情
commitRecord: [], // 提交记录
userCode: '', // 保存当前用户输入的代码
isUpdateCode: false, // 是否更新了代码内容
userCodeTab: 'task', // 学员测评tab位置: task | record | comment
userTestInput: '', // 用户自定义输入值
};
const ojForUserReducer = (state = initialState, action) => {
switch (action.type) {
case types.SAVE_USER_PROGRAM_ID:
return {
...state,
user_program_identifier: action.payload
}
case types.USER_PROGRAM_DETAIL:
const { hack, test_case } = action.payload;
const { code }= hack;
let tempCode = Base64.decode(code)
Object.assign(hack, {code: tempCode});
return {
...state,
hack: Object.assign({}, hack),
test_case: Object.assign({}, test_case)
}
case types.COMMIT_RECORD_DETAIL:
let result = action.payload;
result['output'] = Base64.decode(result['output']);
result['error_msg'] = Base64.decode(result['error_msg']);
return {
...state,
commitRecordDetail: Object.assign({}, result)
}
case types.COMMIT_RECORD:
return {
...state,
commitRecord: [...action.payload]
}
case types.SAVE_USER_CODE:
let curCode = Base64.encode(action.payload);
return {
...state,
userCode: curCode,
isUpdateCode: true
}
case types.IS_UPDATE_CODE:
return {
...state,
isUpdateCode: action.payload
}
case types.CHANGE_USER_CODE_TAB:
return {
...state,
userCodeTab: action.payload
}
default:
return state;
}
}
export default ojForUserReducer;

@ -4,8 +4,9 @@
* @Github: * @Github:
* @Date: 2019-11-20 16:40:32 * @Date: 2019-11-20 16:40:32
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 20:08:07 * @LastEditTime: 2019-11-27 23:39:56
*/ */
import { Base64 } from 'js-base64';
import types from '../actions/actionTypes'; import types from '../actions/actionTypes';
const init = { const init = {
@ -48,6 +49,7 @@ const init = {
errMsg: '' errMsg: ''
} }
}, },
ojTestCaseValidate: [],
testCases: [ testCases: [
// { // {
// input: "11 22", // input: "11 22",
@ -59,9 +61,28 @@ const init = {
position: 1, // TODO 每次加载信息时同步指定positio值 position: 1, // TODO 每次加载信息时同步指定positio值
score: 200, // 分值: 选择难易度后自动计算分值 200 | 500 | 1000 score: 200, // 分值: 选择难易度后自动计算分值 200 | 500 | 1000
code: '', // 提交的代码 code: '', // 提交的代码
identifier: '' // OJ表单id identifier: '', // OJ表单id
loading: false, // 僵尸loading标志
testCodeStatus: 'default', // 调试代码状态 default(默认值) | loading(加载中) | loaded(加载完成) | userCase(用户自定义测试用例) | finish(测试完成)
} }
const tcValidateObj = {
input: {
errMsg: '',
validateStatus: ''
},
output: {
errMsg: '',
validateStatus: ''
}
}
const scoreMaps = {
1: 200,
2: 500,
3: 1000
};
const initialState = Object.assign({}, init); const initialState = Object.assign({}, init);
const ojFormReducer = (state = initialState, action) => { const ojFormReducer = (state = initialState, action) => {
@ -70,7 +91,6 @@ const ojFormReducer = (state = initialState, action) => {
if (action.payload) { if (action.payload) {
ojFormValidate = action.payload.ojFormValidate; ojFormValidate = action.payload.ojFormValidate;
ojForm = action.payload.ojForm; ojForm = action.payload.ojForm;
} }
const returnState = (state, ojForm, ojFormValidate) => { const returnState = (state, ojForm, ojFormValidate) => {
@ -97,9 +117,9 @@ const ojFormReducer = (state = initialState, action) => {
case types.VALIDATE_OJ_LANGUAGE: case types.VALIDATE_OJ_LANGUAGE:
return returnState(state, ojForm, ojFormValidate); return returnState(state, ojForm, ojFormValidate);
case types.VALIDATE_OJ_DIFFICULT: case types.VALIDATE_OJ_DIFFICULT:
const difficult = action.payload.ojForm.difficult.trim(); const curDifficult = action.payload.ojForm.difficult.trim();
if (action.payload.ojForm.difficult) { if (action.payload.ojForm.difficult) {
state.score = scoreMaps[`${difficult}`]; state.score = scoreMaps[`${curDifficult}`];
} }
return returnState(state, ojForm, ojFormValidate); return returnState(state, ojForm, ojFormValidate);
case types.VALIDATE_OJ_CATEGORY: case types.VALIDATE_OJ_CATEGORY:
@ -109,18 +129,24 @@ const ojFormReducer = (state = initialState, action) => {
case types.VALIDATE_OJ_TIMELIMIT: case types.VALIDATE_OJ_TIMELIMIT:
return returnState(state, ojForm, ojFormValidate); return returnState(state, ojForm, ojFormValidate);
case types.ADD_TEST_CASE: case types.ADD_TEST_CASE:
state.testCases.push(action.payload); // 新增测试用例及验证
const tcValidate = tcValidateObj;
const tcVArrs = state.ojTestCaseValidate.concat([tcValidate]);
// state.testCases.push(action.payload);
const tcArrs = state.testCases.concat(action.payload);
state.position++; // 位置递增 state.position++; // 位置递增
return { return {
...state ...state,
testCases: [...tcArrs],
ojTestCaseValidate: [...tcVArrs]
}; };
case types.DELETE_TEST_CASE: case types.DELETE_TEST_CASE:
const { position } = action.payload; const { position } = action.payload;
const { testCases } = state;
// 根据 position 去查找当前元素在数组中的位置 // 根据 position 去查找当前元素在数组中的位置
const index = testCases.findIndex((item) => item.position === position); const index = state.testCases.findIndex((item) => item.position === position);
if (index > -1) { if (index > -1) {
testCases.splice(index, 1); // 删除当前元素 state.testCases.splice(index, 1); // 删除当前元素
state.ojTestCaseValidate.splice(index, 1); // 删除测试用例对应的校验
} }
return { return {
...state ...state
@ -130,11 +156,108 @@ const ojFormReducer = (state = initialState, action) => {
return { return {
...state ...state
} }
case types.SAVE_EDIT_OJ_FORM_AND_TEST_CASE: // 保存编辑的值
/**
* 1. 将当前值保存至OJForm中
* 2. 将当前的测试用例保存至 testCases中 并增加 isAdd: false 属性
* 3. 设置position的值, 即新增下一个测试用例的位置
* 4. 自定义测试用例是否需要返回
* 5. 代码执行的结果
* 6. 更改测试用例状态
* 7. 添加测试用例验证
*/
const { code = '', description, language, name, hack_sets = [], time_limit, difficult, category } = action.payload;
const currentOjForm = {
name, // 任务名称
language,
description,
difficult,
category,
openOrNot: 1,
timeLimit: time_limit
};
// state.code = code; // 保存代码块值
let curPosition = 0;
const curTestCases = [];
const curTcValidates = [];
hack_sets.forEach(hack => {
if (hack.position > curPosition) {
curPosition = hack.position;
}
curTcValidates.push(tcValidateObj); // 一个测试用例对应一个校验
curTestCases.push(Object.assign({}, hack, { isAdd: false }));
// state.testCases.push(Object.assign({}, hack, { isAdd: false }));
});
let cbcode = '';
if (typeof code === 'string') {
cbcode = Base64.decode(code);
} else if (Array.isArray(code)) {
cbcode = Base64.decode(code[code.length - 1]);
}
// state.position = curPosition; // 计算下一个测试用例的位置值
return {
...state,
ojForm: currentOjForm,
position: curPosition + 1,
code: cbcode,
testCases: curTestCases,
testCodeStatus: hack_sets.length > 0 ? 'userCase' : 'default'
}
case types.CLEAR_JSFORM_STORE: case types.CLEAR_JSFORM_STORE:
state = Object.assign({}, init); state = Object.assign({}, init);
return { return {
...state ...state
} }
// case types.TEST_CODE_STATUS:
// return {
// ...state,
// testCodeStatus: action.payload // 当前状态值
// }
case types.VALIDATE_TEST_CODE_ARRS:
return {
...state,
ojTestCaseValidate: action.payload
}
case types.TEST_CASE_INPUT_CHANGE:
const { input } = action.payload;
// 更新验证消息
const curIOjTestValidate = state.ojTestCaseValidate.map((tc, i) => {
if (i === action.payload.index) {
return Object.assign({}, tc, {input});
}
return tc;
});
let curITestValues = state.testCases.map((tc, i) => {
if (i === action.payload.index) {
return Object.assign({}, tc, { input: action.payload.value })
}
return tc;
});
return {
...state,
ojTestCaseValidate: [...curIOjTestValidate],
testCases: [...curITestValues]
}
case types.TEST_CASE_OUTPUT_CHANGE:
const { output } = action.payload;
// 更新验证消息
const curOOjTestValidate = state.ojTestCaseValidate.map((tc, i) => {
if (i === action.payload.index) {
return Object.assign({}, tc, {output});
}
return tc;
});
let curOTestValues = state.testCases.map((tc, i) => {
if (i === action.payload.index) {
return Object.assign({}, tc, { output: action.payload.value })
}
return tc;
});
return {
...state,
ojTestCaseValidate: [...curOOjTestValidate],
testCases: [...curOTestValues]
}
default: default:
return state; return state;
} }

@ -4,7 +4,7 @@
* @Github: * @Github:
* @Date: 2019-11-20 10:55:38 * @Date: 2019-11-20 10:55:38
* @LastEditors: tangjiang * @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 16:48:35 * @LastEditTime: 2019-11-28 18:58:20
*/ */
import axios from 'axios'; import axios from 'axios';
@ -21,9 +21,16 @@ export async function fetchOJList (params) {
} }
// 提交 // 提交
export async function fetchPostOjForm (params) { export async function fetchPostOjForm (paramsObj) {
const url = `/problems.json`; const { params, submitType, identifier } = paramsObj;
return axios.post(url, params); const url = submitType === 'add' ? `/problems.json` : `/problems/${identifier}.json`;
// return axios.post(url, params);
// if (identifier) {
// return axios.post(url, params);
// } else {
// return
// }
return identifier ? axios.put(url, params) : axios.post(url, params);
} }
// 根据id号获取OJ信息 // 根据id号获取OJ信息
@ -32,5 +39,62 @@ export async function fetchGetOjById (id) {
return axios.get(url); return axios.get(url);
} }
// 调试代码
export async function fetchDebuggerCode (identifier, params) {
const url = `/myproblems/${identifier}/code_debug.json`;
return axios.get(url, {params});
}
// 调试代码成功后,循环调用提交接口
export async function fetchCodeSubmit (identifier, params) {
const url = `/myproblems/${identifier}/result.json`;
return axios.get(url, {params});
}
// 开启编程题接口
export async function fetchStartProgram (identifier) {
const url = `/problems/${identifier}/start.json`;
return axios.get(url);
}
// 用户编程题详情
export async function fetchUserProgramDetail (identifier) {
const url = `/myproblems/${identifier}.json`;
return axios.get(url);
}
// 获取提交记录
export async function fetchUserCommitRecord (identifier) {
const url = `/myproblems/${identifier}/submit_records.json`;
return axios.get(url);
}
// 获取提交记录详情
export async function fetchUserCommitRecordDetail () {
const url = `/myproblems/record_detail.json`;
return axios.get(url);
}
// 恢复初始代码
export async function restoreInitialCode (identifier) {
const url = `/myproblems/${identifier}/restore_initial_code.json`;
return axios.get(url);
}
// 发布任务
export async function publishTask (identifier) {
const url = `/problems/${identifier}/publish.json`;
return axios.post(url);
}
// 更新用户编辑代码
export async function fetchUpdateCode (identifier, params) {
const url = `/myproblems/${identifier}/update_code.json`;
return axios.post(url, params);
}
// 用户提交代码
export async function fetchUserCodeSubmit (identifier) {
const url = `/myproblems/${identifier}/code_submit.json`;
return axios.get(url);
}

Loading…
Cancel
Save