开发者社区代码保存

dev_forge
tangjiang 5 years ago
parent b3f9dae41a
commit 12b725d215

@ -29,6 +29,7 @@
"dotenv": "4.0.0",
"dotenv-expand": "4.2.0",
"echarts": "^4.2.0-rc.2",
"editor.md": "^1.5.0",
"eslint": "4.10.0",
"eslint-config-react-app": "^2.1.0",
"eslint-loader": "1.9.0",

@ -305,6 +305,11 @@ const NewOrEditTask = Loadable({
loader: () => import('./modules/developer/newOrEditTask'),
loading: Loading
});
// 学员学习
const StudentStudy = Loadable({
loader: () => import('./modules/developer/studentStudy'),
loading: Loading
});
// //个人竞赛报名
// const PersonalCompetit = Loadable({
// loader: () => import('./modules/competition/personal/PersonalCompetit.js'),
@ -483,8 +488,6 @@ class App extends Component {
<Router>
<Switch>
<Route path="/developer/neworedittask" component={NewOrEditTask} />
{/*题库*/}
<Route path="/topicbank/:username/:topicstype"
render={
@ -660,7 +663,9 @@ class App extends Component {
(props)=>(<Ecs {...this.props} {...props} {...this.state}></Ecs>)
}/>
<Route component={Developer} />
<Route path="/developer/neworedittask/:id?" component={NewOrEditTask} />
<Route path="/developer/studentstudy/:id?" component={StudentStudy} />
<Route path="/developer" component={Developer}/>
<Route exact path="/"
// component={ShixunsHome}

@ -0,0 +1,19 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-20 23:10:48
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 10:05:42
*/
const jcLabel = {
name: '任务名称',
language: '编程语言',
description: '描述',
difficult: '难易度',
category: '分类',
openOrNot: '公开程序',
timeLimit: '时间限制'
}
export default jcLabel;

@ -5,17 +5,18 @@
* @Last Modified by: tangjiang
* @Last Modified time: 2019-11-18 16:52:38
*/
import './index.scss';
import React, { PureComponent } from 'react';
import { TPMIndexHOC } from '../tpm/TPMIndexHOC';
import { Table, Button, Dropdown, Icon, Menu, Card, Input } from 'antd';
import { Table, Button, Dropdown, Icon, Menu, Card, Input, Select, Tag } from 'antd';
import { connect } from 'react-redux';
import actions from '../../redux/actions';
import MultipTags from './components/multiptags';
import { Link } from 'react-router-dom';
const { Search } = Input;
const { Option } = Select;
// import reqwest from 'reqwest';
/**
* 下拉菜单
@ -40,89 +41,117 @@ const maps = {
],
'hardMenu': [
{
'key': '0',
'key': '1',
'name': '简单',
'vlaue': '0'
'vlaue': '1'
},
{
'key': '1',
'key': '2',
'name': '中等',
'vlaue': '1'
'vlaue': '2'
},
{
'key': '2',
'key': '3',
'name': '困难',
'vlaue': '2'
'vlaue': '3'
}
],
'statusMenu': [
{
'key': '0',
'key': '-1',
'name': '未做',
'vlaue': '-1'
},
{
'key': '0',
'name': '未通过',
'vlaue': '0'
},
{
'key': '1',
'name': '已通过',
'vlaue': '1'
},
{
'key': '2',
'name': '未通过',
'vlaue': '2'
}
],
'originMenu': [
{
'key': '0',
'key': 'all',
'name': '全部',
'vlaue': '0'
'vlaue': 'all'
},
{
'key': '1',
'key': 'mine',
'name': '我创建的',
'vlaue': '1'
'vlaue': 'mine'
}
]
};
const testMaps = {
category: {
1: '程序设计基础',
2: '数据结构与算法'
},
difficult: {
1: '简单',
2: '中等',
3: '困难'
},
type: {
1: '#52c41a',
2: '#faad14',
3: '#f5222d'
}
}
/**
* 表格列
*/
const columns = [
{
title: '序号',
dataIndex: 'id',
width: '10%'
},
// {
// title: '序号',
// dataIndex: 'identifier',
// width: '10%'
// },
{
title: '标题',
dataIndex: 'title',
dataIndex: 'name',
render: (name, record) => <Link to={`/developer/neworedittask/${record.identifier}`}>{name}</Link>
},
{
title: '分类',
dataIndex: 'category',
width: '20%'
width: '20%',
align: 'center',
render: (category) => <span>{category ? testMaps['category'][+category] : '-'}</span>
},
{
title: '难度',
dataIndex: 'level',
dataIndex: 'difficult',
align: 'center',
width: '10%'
width: '10%',
render: (difficult) => {
if (difficult) {
return <Tag color={testMaps['type'][+difficult]}>{testMaps['difficult'][+difficult]}</Tag>
} else {
return '-';
}
}
},
{
title: '热度',
dataIndex: 'hot',
dataIndex: 'hack_user_lastest_codes_count',
sorter: true,
align: 'center',
width: '10%'
},
{
title: '通过率',
dataIndex: 'pass',
dataIndex: 'passed_rate',
sorter: true,
align:'right',
width: '10%'
width: '10%',
render: val => <span>{`${val}%`}</span>
}
];
@ -130,52 +159,41 @@ class DeveloperHome extends PureComponent {
state = {
data: [],
pagination: {
total: 1, // 总条数
pageSize: 10, // 每页显示条数
current: 1, // 当前页数
showQuickJumper: true
},
loading: false,
searchParams: {
search: '', // 查询关键字
'come_from': '', // 来源
difficult: '', // 难易度
status: '', // 未做
category: '', // 分类
'sort_by': '', // 排序
'sort_direction': '' // 排序方向
}
};
componentDidMount() {
this.fetch();
this.fetchData();
const {hacks_count} = this.props.ojListReducer;
this.setState({
pagination: {
total: hacks_count
}
});
}
handleTableChange = (pagination, filters, sorter) => {
const pager = { ...this.state.pagination };
pager.current = pagination.current;
this.setState({
pagination: pager,
});
this.fetch({
results: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters,
});
// console.log(pagination, filters, sorter);
const {field, order} = sorter;
this.handleFilterSearch({sort_by: field, sort_direction: order === 'descend' ? 'desc' : 'asc'});
};
fetch = (params = {}) => {
console.log('params:', params);
this.setState({ loading: true });
// reqwest({
// url: 'https://randomuser.me/api',
// method: 'get',
// data: {
// results: 10,
// ...params,
// },
// type: 'json',
// }).then(data => {
// const pagination = { ...this.state.pagination };
// // Read total count from server
// // pagination.total = data.totalCount;
// pagination.total = 200;
// this.setState({
// loading: false,
// data: data.results,
// pagination,
// });
// });
fetchData = () => {
this.props.fetchOJList(this.state.searchParams);
};
/**
@ -198,27 +216,55 @@ class DeveloperHome extends PureComponent {
</Menu>
)
};
getOptionsItem = (type) => {
return maps[type].map(item => {
return <Option key={item.key} value={item.value}>{item.name}</Option>
});
}
// 点击条件时加载数据
handleFilterSearch = (obj) => {
const searchParams = Object.assign({}, this.state.searchParams, obj);
this.setState({
searchParams: searchParams
}, () => {
this.fetchData();
});
}
/**
* 搜索输入框
* @param value 输入框值
*/
handleInputSearch = (value) => {
console.log('搜索值==', value);
value = value.trim();
// if (value.length === 0) return;
this.handleFilterSearch({search: value});
}
// handleSearchChange = (e) => {
// console.log(e.target.value);
// const value = e.target.value.trim();
// }
// 下拉类别菜单
handleCategoryMenuClick = (e) => {
console.log('dropdown ==>>>', e);
handleCategoryMenuClick = (item) => {
this.handleFilterSearch({category: +item.key === 0 ? '' : +item.key});
}
// 难度下拉
handleHardMenuClick = (e) => {}
handleHardMenuClick = (item) => {
this.handleFilterSearch({difficult: +item.key});
}
// 状态下拉
handleSatusMenuClick = (e) => {}
handleSatusMenuClick = (item) => {
this.handleFilterSearch({status: +item.key});
}
// 来源下拉
handleOriginMenuClick = (e) => {}
handleOriginMenuClick = (item) => {
this.handleFilterSearch({come_from: item.key === 'all' ? '' : item.key});
}
render () {
// const { testReducer, handleClick } = 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;
// console.log( '======>>>>>>', ojListReducer );
return (
<div className="developer-list">
<div className="ant-spin-container">
@ -226,11 +272,11 @@ class DeveloperHome extends PureComponent {
<div className="educontent">
<div className={'card-top'}>
<div className="search-params">
<p className={'save-question'}>已解决 <span className={''}>589</span> / 1800 </p>
<p className={'save-question'}>已解决 <span className={''}>{passed_count}</span> / {hacks_count} </p>
<div className={'question-level'}>
<MultipTags type="success" text="简单" numb="1200" style={{ marginRight: '20px' }}/>
<MultipTags type="warning" text="中等" numb="400" style={{ marginRight: '20px' }}/>
<MultipTags type="error" text="困难" numb="0"/>
<MultipTags type="success" text="简单" numb={simple_count} style={{ marginRight: '20px' }}/>
<MultipTags type="warning" text="中等" numb={medium_count} style={{ marginRight: '20px' }}/>
<MultipTags type="error" text="困难" numb={diff_count}/>
</div>
<Button type="primary">
<Link to="/developer/neworedittask">新建</Link>
@ -239,21 +285,22 @@ class DeveloperHome extends PureComponent {
</div>
<div className={'card-table'}>
<Card bordered={false}>
<Dropdown className={'dropdonw-style'} placement="bottomCenter" overlay={this.getMenuItems('categoryMenu', this.handleCategoryMenuClick)}>
<Dropdown className={'dropdonw-style'} placement="bottomLeft" overlay={this.getMenuItems('categoryMenu', this.handleCategoryMenuClick)}>
<span className={'dropdown-span'}>分类 <Icon type="down"/></span>
</Dropdown>
<Dropdown className={'dropdonw-style'} placement="bottomCenter" overlay={this.getMenuItems('hardMenu', this.handleHardMenuClick)}>
<Dropdown className={'dropdonw-style'} placement="bottomLeft" overlay={this.getMenuItems('hardMenu', this.handleHardMenuClick)}>
<span className={'dropdown-span'}>难度 <Icon type="down"/></span>
</Dropdown>
<Dropdown className={'dropdonw-style'} placement="bottomCenter" overlay={this.getMenuItems('statusMenu', this.handleSatusMenuClick)}>
<Dropdown className={'dropdonw-style'} placement="bottomLeft" overlay={this.getMenuItems('statusMenu', this.handleSatusMenuClick)}>
<span className={'dropdown-span'}>状态 <Icon type="down"/></span>
</Dropdown>
<Dropdown className={'dropdonw-style'} placement="bottomCenter" overlay={this.getMenuItems('originMenu', this.handleOriginMenuClick)}>
<Dropdown className={'dropdonw-style'} placement="bottomLeft" overlay={this.getMenuItems('originMenu', this.handleOriginMenuClick)}>
<span className={'dropdown-span'}>来源 <Icon type="down"/></span>
</Dropdown>
<Search
placeholder="输入标题进行搜索"
onChange={this.handleSearchChange}
onSearch={value => this.handleInputSearch(value)}
style={{ width: 320, float: 'right' }}
/>
@ -263,7 +310,7 @@ class DeveloperHome extends PureComponent {
<Table
columns={columns}
rowKey={record => Math.random()}
dataSource={this.state.data}
dataSource={hacks_list}
pagination={this.state.pagination}
onChange={this.handleTableChange}
/>
@ -281,12 +328,14 @@ class DeveloperHome extends PureComponent {
* @param {*} ownProps DeveloperHome 中的 props
*/
const mapStateToProps = (state, ownProps) => ({
testReducer: state.testReducer
testReducer: state.testReducer,
ojListReducer: state.ojListReducer
});
const mapDispatchToProps = (dispatch) => ({
handleClick: () => dispatch(actions.toggleTodo())
handleClick: () => dispatch(actions.toggleTodo()),
fetchOJList: (params) => dispatch(actions.getOJList(params))
});
export default connect(

@ -11,9 +11,7 @@ import DeveloperHome from './DeveloperHome';
const App = () => {
return (
<div className="developer-list">
<DeveloperHome />
</div>
<DeveloperHome />
);
}

@ -9,7 +9,10 @@
}
.developer-list{
overflow: hidden;
// overflow: hidden;
.ant-spin-container{
padding-bottom: 100px;
}
.card-top {
border-radius:4px;
background:rgba(255,255,255,1);
@ -50,4 +53,4 @@
}
}
}
}
}

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

@ -6,45 +6,37 @@
* @Last Modified time: 2019-11-19 23:23:41
*/
import './index.scss';
import React from 'react';
import React, { useCallback, useEffect } from 'react';
import { connect } from 'react-redux';
import SplitPane from 'react-split-pane';// import { Form } from 'antd';
import { Button, Icon } from 'antd';
import { Link } from 'react-router-dom';
import LeftPane from './leftpane';
import RightPane from './rightpane/index';
import actions from '../../../redux/actions';
// class NewOrEditTask extends React.Component {
// render() {
// return (
// <div className={'new_add_task_wrap'}>
// <div className={'task_header'}>
// <Link to="/" className={'header_btn'} >
// <Icon type="left" style={{ marginRight: '5px'}}/>后退
// </Link>
// <span className={'header_title'}>标题内容</span>
// <Button className={`header_btn`} type="primary">立即发布</Button>
// </div>
// <div className="split-pane-area">
// <SplitPane split="vertical" minSize={200} maxSize={-200} defaultSize="50%">
// <div className={'split-pane-left'}>
// <LeftPane />
// </div>
// <SplitPane split="vertical" defaultSize="100%" allowResize={false}>
// <RightPane />
// <div />
// </SplitPane>
// </SplitPane>
// </div>
// </div>
// )
// }
// }
const NewOrEditTask = () => {
const NewOrEditTask = (props) => {
// 表单提交
const handleSubmitForm = () => {}
const handleSubmitForm = (code) => {
props.saveOjFormCode(code); // 保存代码块值
props.handleFormSubmit(); // 提交表单
};
useEffect(() => {
console.log('获取路由参数: ====', props.match.params);
const id = props.match.params.id;
// 保存OJForm的id号指明是编辑还是新增
props.saveOJFormId(id);
if (id) { // id号即 identifier
// TODO id 存在时, 编辑, 获取 store 中的记录数
// props.getOJFormById(id);
} else {
// 清空store中的测试用例集合
props.clearOJFormStore();
}
}, []);
return (
<div className={'new_add_task_wrap'}>
<div className={'task_header'}>
@ -57,12 +49,10 @@ const NewOrEditTask = () => {
<div className="split-pane-area">
<SplitPane split="vertical" minSize={200} maxSize={-200} defaultSize="50%">
<div className={'split-pane-left'}>
<LeftPane />
<LeftPane/>
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane
onSubmit={ handleSubmitForm }
/>
<RightPane onSubmitForm={handleSubmitForm}/>
<div />
</SplitPane>
</SplitPane>
@ -75,7 +65,18 @@ const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) => ({});
const mapDispatchToProps = (dispatch) => ({
// 保存提交的代码值
saveOjFormCode: (value) => dispatch(actions.saveOjFormCode(value)),
// 表单提交时,调用表单验证功能
handleFormSubmit: () => dispatch(actions.validateOjForm()),
// 根据id号获取表单信息
getOJFormById: (id) => dispatch(actions.getOJFormById(id)),
// 保存 OJ form id值
saveOJFormId: (id) => dispatch(actions.saveOJFormId(id)),
// 清空测试用例的集合
clearOJFormStore: () => dispatch(actions.clearOJFormStore()),
});
export default connect(
mapStateToProps,

@ -1,80 +1,2 @@
.new_add_task_wrap{
height: 100vh;
.task_header{
display: flex;
align-items: center;
// justify-content: space-between;
height: 65px;
background:rgba(34,34,34,1);
padding:0 30px;
@import '../split_pane_resizer.scss';
.header_btn,
.header_title{
color: #fff;
}
.header_btn{
width: 88px;
}
.header_title{
flex: 1;
text-align: center;
}
}
.split-pane-area{
position: relative;
height: calc(100% - 65px);
.left_pane,
.right_pane{
height: 100%;
}
}
}
.Resizer {
background: #000;
opacity: 0.2;
z-index: 1;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding-box;
}
.Resizer:hover {
-webkit-transition: all 2s ease;
transition: all 2s ease;
}
.Resizer.horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
width: 100%;
}
.Resizer.horizontal:hover {
border-top: 5px solid rgba(0, 0, 0, 0.5);
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.vertical {
width: 11px;
margin: 0 -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize;
}
.Resizer.vertical:hover {
border-left: 5px solid rgba(0, 0, 0, 0.5);
border-right: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.disabled {
cursor: not-allowed;
}
.Resizer.disabled:hover {
border-color: transparent;
}

@ -0,0 +1,159 @@
/*
* @Description: 添加测试用例
* @Author: tangjiang
* @Github:
* @Date: 2019-11-21 09:19:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 19:45:48
*/
import './index.scss';
import React, { useEffect, useState } from 'react';
import { Collapse, Icon, Input, Form, Button, Modal } from 'antd';
import { connect } from 'react-redux';
const { Panel } = Collapse;
const { TextArea } = Input;
const FormItem = Form.Item;
const AddTestDemo = (props) => {
const {
form: { getFieldDecorator },
onSubmitTest,
onDeleteTest,
testCase,
key
} = props;
const [isEditor, setIsEditor] = useState(false);
const [loading, setLoading] = useState(false);
// console.log('测试用例属性: ====>>>>', props);
// 删除操作
const handleDeletePanel = (e) => {
e.stopPropagation();
e.preventDefault();
// console.log('点击的删除按钮')
Modal.confirm({
title: '删除',
content: '确定要删除当前测试用例吗?',
okText: '确定',
cancelText: '取消',
onOk() {
console.log('确定删除');
onDeleteTest(testCase);
}
})
}
// 右侧删除图标
const genExtra = () => (
<Icon
type="close"
onClick={handleDeletePanel}
/>
)
// 取消操作
const handleReset = (e) => {
e.preventDefault();
props.form.resetFields();
}
// 保存
const handleSubmit = (e) => {
e.preventDefault();
props.form.validateFields((err, values) => {
if (err) {
return;
}
console.log('提交表单: ', values);
onSubmitTest(values);
});
}
// 编辑后保存
const handleEditorOrSave = (e) => {
if (!isEditor) {
setIsEditor(true);
} else {
// TODO 调用修改测试用例接口
setIsEditor(false); // 保存后 设置 false
setLoading(true);
}
}
const renderSubmitBtn = () => {
const { identifier, testCase } = props;
// console.log('========', identifier);
// 1. 新增时,不显示按钮
if (identifier) {
if (testCase.isAdd) {
return (
<FormItem style={{ textAlign: 'right' }}>
<Button style={{ marginRight: '20px' }} onClick={handleReset}>取消</Button>
<Button type="primary" onClick={handleSubmit}>保存</Button>
</FormItem>
);
} else {
return (
<FormItem style={{ textAlign: 'right' }}>
<Button onClick={handleEditorOrSave} loading={loading}>{isEditor ? '保存' : (loading ? '保存' : '编辑')}</Button>
</FormItem>
);
}
}
}
// 指定文本框是否可编辑
const isDisabled = (testCase) => {
return testCase.isAdd && !isEditor;
};
return (
<Collapse className={'collapse_area'}>
<Panel header={`测试用例${testCase.position}`} extra={genExtra()} key={key}>
<Form>
<FormItem
label='输入'
>
{
getFieldDecorator('input', {
rules: [
{ required: true, message: '输入值不能为空'}
],
initialValue: testCase && testCase.input
})(<TextArea rows={5} disabled={isDisabled(testCase)}/>)
}
</FormItem>
<FormItem label="输出">
{
getFieldDecorator('output', {
rules: [
{required: true, message: '输出值不能为空'}
],
initialValue: testCase && testCase.output
})(<Input disabled={isDisabled(testCase)}/>)
}
</FormItem>
{/* <FormItem style={{ textAlign: 'right' }}>
<Button style={{ marginRight: '20px' }} onClick={handleReset}>取消</Button>
<Button type="primary" onClick={handleSubmit}>保存</Button>
</FormItem> */}
{renderSubmitBtn()}
</Form>
</Panel>
</Collapse>
);
}
const mapStateToProps = (state) => {
const ojFormReducer = state.ojFormReducer;
return {
identifier: ojFormReducer.identifier
}
};
const mapDispatchToProps = (dispatch) => ({
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form.create()(AddTestDemo));

@ -1,41 +1,288 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-20 10:35:40
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 19:10:53
*/
import './index.scss';
import React, { PureComponent } from 'react';
import { Form, Input, Button } from 'antd';
import { Form, Input, Select, InputNumber, Button } from 'antd';
import { connect } from 'react-redux';
import AddTestDemo from './AddTestDemo';
import actions from '../../../../../redux/actions';
import jcLabel from '../../../../../constants';
import MEditor from '../../../meditor/MEditor';
// import { getUrl } from 'educoder';
// const path = getUrl("/editormd/lib/")
// const mEditor = require('editor.md');
// console.log(mEditor);
const FormItem = Form.Item;
const { Option } = Select;
const { TextArea } = Input;
const maps = {
language: [
{ title: 'C', key: 'C' },
{ title: 'C++', key: 'C++' },
{ title: 'Python', key: 'Python' },
{ title: 'Java', key: 'Java' }
],
difficult: [
{ title: '简单', key: '1' },
{ title: '中等', key: '2'},
{ title: '困难', key: '3' }
],
category: [
{ title: '程序设计', key: '1' },
{ title: '算法', key: '2'}
],
openOrNot: [
{ title: '公开', key: '1' },
{ title: '私有', key: '0' }
]
}
class EditTab extends PureComponent {
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, value) => {
if (!err) {
console.log(value);
}
})
constructor (props) {
super(props);
this.editorRef = React.createRef();
}
componentDidMount () {
// this.createEditorMd('editormd_section', '100%', 400, '', '', '', path);
}
// 改变任务名称
handleNameChange = (e) => {
const value = e.target.value;
const {
validateOJName,
// validateOjLanguage,
// validateOjDescription,
// validateOjDifficult,
// validateOjTimeLimit,
// validateOjCategory,
// validateOpenOrNot
} = this.props;
validateOJName(value);
}
// 改变语言
handleLanguageChange = (value) => {
this.props.validateOjLanguage(value);
}
// 改变描述信息
handleChangeDescription = (e) => {
const value = e.target.value;
this.props.validateOjDescription(value);
}
// 改变难易度
handleChangeDifficult = (value) => {
this.props.validateOjDifficult(value);
}
// 改变时间限制
handleTimeLimitChange = (value) => {
this.props.validateOjTimeLimit(value);
}
// 改变分类
handleChangeCategory = (value) => {
this.props.validateOjCategory(value);
}
// 改变公开程序
handleChangeOpenOrNot = (value) => {
this.props.validateOpenOrNot(value);
}
render () {
const { form } = this.props;
const { getFieldDecorator } = form;
const {
ojFormReducer: {ojForm, ojFormValidate},
testCases = [], // 测试用例集合
position, // 添加的测试用例位置
addTestCase, // 添加测试用例
deleteTestCase, // 删除测试用例
} = this.props;
console.log('当前位置: ', position);
// 表单label
const myLabel = (name, subTitle) => {
if (subTitle) {
return (
<span className={'label_text'}>
{name}
<span className={'label_sub_text'}>
({subTitle})
</span>
</span>
)
} else {
return (
<span className={'label_text'}>{name}</span>
)
}
};
// 编程语言
const getOptions = (key) => {
return maps[key].map((opt, i) => {
return (
<Option value={opt.key} key={`opt_${i}`}>{opt.title}</Option>
);
});
};
// 提交测试用例
const handleSubmitTest = (obj) => {
console.log('提交的测试用例: ', obj);
};
// 删除测试用例
const handleDeleteTest = (obj) => {
console.log('删除的测试用例: ', obj);
deleteTestCase(obj);
};
const renderTestCase = () => {
return testCases.map((item, i) => (
<AddTestDemo
key={`key_${i}`}
onSubmitTest={handleSubmitTest}
onDeleteTest={handleDeleteTest}
testCase={item}
/>
));
};
// 添加测试用例
const handleAddTest = () => {
console.log('添加测试用例');
const obj = {
input: '',
output: '',
position: position,
isAdd: true // 新增的测试用例
}
addTestCase(obj);
// TODO 点击新增时,需要滚到到最底部
// this.editorRef.current.scrollTop
// const oDiv = this.editorRef.current;
// oDiv.scrollTo(oDiv.scrollLeft, 99999);
// console.log(oDiv.scrollTop);
// oDiv.scrollTop = 99999;
}
return (
<div className={'editor_area'}>
<Form onSubmit={this.handleSubmit}>
<FormItem label="任务名称">
{ getFieldDecorator('name', {
rules: [
{
required: true, message: '任务名称不能为空'
}
]
})(<Input placeholder="请输入任务名称" />)}
<div className={'editor_area'} ref={this.editorRef}>
<Form className={'editor_form'}>
<FormItem
className={`input_area flex_60`}
label={<span>{myLabel(jcLabel['name'])}</span>}
validateStatus={ojFormValidate.name.validateStatus}
help={ojFormValidate.name.errMsg}
colon={ false }
>
<Input
maxLength={60}
placeholder="请输入任务名称"
value={ojForm.name}
suffix={<span style={{ fontSize: '12px', color: 'rgba(0, 0, 0, 0.45)' }}>{60 - ojForm.name.length}</span>}
onChange={this.handleNameChange}
/>
</FormItem>
<FormItem
className={`input_area flex_40`}
label={<span>{myLabel(jcLabel['language'])}</span>}
validateStatus={ojFormValidate.language.validateStatus}
help={ojFormValidate.language.errMsg}
colon={ false }
>
<Select onChange={this.handleLanguageChange} defaultValue={`${ojForm.language}`}>
{getOptions('language')}
</Select>
</FormItem>
<FormItem
className={`input_area flex_100`}
label={<span>{myLabel(jcLabel['description'])}</span>}
validateStatus={ojFormValidate.description.validateStatus}
help={ojFormValidate.description.errMsg}
colon={ false }
>
<TextArea rows={5} onChange={this.handleChangeDescription}/>
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_left`}
label={<span>{myLabel(jcLabel['difficult'], '任务的难易程度')}</span>}
validateStatus={ojFormValidate.difficult.validateStatus}
help={ojFormValidate.difficult.errMsg}
colon={ false }
>
<Select onChange={this.handleChangeDifficult} defaultValue={`${ojForm.difficult}`}>
{getOptions('difficult')}
</Select>
</FormItem>
<FormItem>
<Button htmlType="submit">提交</Button>
<FormItem
className={`input_area flex_50 flex_50_right`}
label={<span>{myLabel(jcLabel['timeLimit'], '程序允许时间限制时长,单位:秒')}</span>}
validateStatus={ojFormValidate.timeLimit.validateStatus}
help={ojFormValidate.timeLimit.errMsg}
colon={ false }
>
<InputNumber min={0} style={{ width: '100%' }} onChange={this.handleTimeLimitChange}/>
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_left`}
label={<span>{myLabel(jcLabel['category'], '任务所属分类')}</span>}
validateStatus={ojFormValidate.category.validateStatus}
help={ojFormValidate.category.errMsg}
colon={ false }
>
<Select onChange={this.handleChangeCategory} defaultValue={`${ojForm.category}`}>
{getOptions('category')}
</Select>
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_right`}
label={<span>{myLabel(jcLabel['openOrNot'], '社区:您的任务将向整个社会公开')}</span>}
validateStatus={ojFormValidate.openOrNot.validateStatus}
help={ojFormValidate.openOrNot.errMsg}
colon={ false }
>
<Select onChange={this.handleChangeOpenOrNot} defaultValue={`${ojForm.openOrNot}`}>
{getOptions('openOrNot')}
</Select>
</FormItem>
</Form>
{/* 添加测试用例 */}
<div className="test_demo_title">
<h2>测试用例</h2>
<Button type="primary" onClick={handleAddTest}>添加测试用例</Button>
</div>
<div className="test_demo_ctx">
{ renderTestCase() }
</div>
</div>
)
}
}
const EditTabForm = Form.create()(EditTab);
export default EditTabForm;
const mapStateToProps = (state) => {
const ojFormReducer = state.ojFormReducer;
return {
ojFormReducer,
testCases: ojFormReducer.testCases, // 测试用例
position: ojFormReducer.position, // 测试用例位置
}
};
const mapDispatchToProps = (dispatch) => ({
// 任务名称校验
validateOJName: (value) => dispatch(actions.validateOJName(value)),
validateOjLanguage: (value) => dispatch(actions.validateOjLanguage(value)),
validateOjDescription: (value) => dispatch(actions.validateOjDescription(value)),
validateOjDifficult: (value) => dispatch(actions.validateOjDifficult(value)),
validateOjTimeLimit: (value) => dispatch(actions.validateOjTimeLimit(value)),
validateOjCategory: (value) => dispatch(actions.validateOjCategory(value)),
validateOpenOrNot: (value) => dispatch(actions.validateOpenOrNot(value)),
// 新增测试用例
addTestCase: (value) => dispatch(actions.addTestCase(value)),
// 删除测试用例
deleteTestCase: (value) => dispatch(actions.deleteTestCase(value)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(EditTab);

@ -0,0 +1,66 @@
.editor_area{
height: calc(100vh - 110px);
overflow-y: auto;
padding: 20px 0;
.editor_form{
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.label_text{
position: relative;
font-size: 14px;
&::before{
display: inline-block;
margin-right: 4px;
color: #f5222d;
font-size: 14px;
font-family: SimSun,sans-serif;
line-height: 1;
content: '*';
}
}
.input_area{
display: inline-block;
&.flex_60{
padding-right: 20px;
width: 60%;
}
&.flex_40{
width: 40%;
}
&.flex_100{
width: 100%;
}
&.flex_50{
width: 50%;
}
&.flex_50_left{
padding-right: 10px;
}
&.flex_50_right{
padding-left: 10px;
}
}
.label_sub_text{
font-size: 12px;
color: #999999;
}
.test_demo_title,
.test_demo_ctx,
.editor_form{
margin: 0 30px;
}
.test_demo_title{
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
border-bottom: 1px solid #d9d9d9;
margin-bottom: 20px;
}
.collapse_area{
margin-bottom: 20px;
}
}

@ -21,7 +21,7 @@ const LeftPane = () => {
const [defaultActiveKey, setDefaultActiveKey] = useState('editor');
const tabArrs = [
{ title: '编辑', key: 'editor', content: (<EditorTab wrappedComponentRef={(ref) => this.form} />) },
{ title: '编辑', key: 'editor', content: (<EditorTab />) },
{ title: '预览', key: 'prev', content: (<PrevTab />) },
{ title: '提交记录', key: 'commit', content: (<CommitTab />) },
];
@ -45,6 +45,6 @@ const LeftPane = () => {
{ tabs }
</Tabs>
)
}
};
export default LeftPane;

@ -2,4 +2,12 @@
.ant-tabs-nav-wrap{
padding: 0 30px;
}
}
.ant-tabs-bar{
margin: 0;
}
// .ant-tabs-tabpane{
// padding-top: 10px;
// height: calc(100vh - 110px);
// overflow: auto;
// }
}

@ -11,7 +11,8 @@ import './index.scss';
import React, { Fragment, useState, useRef } from 'react';
import { Icon, Drawer, Tabs, Button, notification } from 'antd';
// import MonacoEditor from 'react-monaco-editor';
import MonacoEditor from '@monaco-editor/react';
import MonacoEditor, { DiffEditor } from '@monaco-editor/react';
import InitTabCtx from './initTabCtx';
const { TabPane } = Tabs;
@ -59,7 +60,7 @@ const RightPaneCode = (props) => {
// 沉浸tab内容
const tabs = tabsArrs.map((tab) => (
<TabPane tab={tab.title} key={tab.key} style={{ height: '280px', overflowY: 'auto' }}>
<InitTabCtx ctx={tab.content} state="loaded" position="center"/>
{/* <InitTabCtx ctx={tab.content} state="loaded" position="center"/> */}
</TabPane>
));
@ -70,11 +71,13 @@ const RightPaneCode = (props) => {
editorRef.current.onDidChangeModelContent(e => { // 监听编辑器内容的变化
const val = editorRef.current.getValue();
setEditCode(val);
// 保存代码值
});
}
// 提交按钮点击
const handleSubmit = () => {
const handleSubmit = (e) => {
e.preventDefault();
if (!editCode) {
notification['error']({
message: '必填',
@ -83,8 +86,8 @@ const RightPaneCode = (props) => {
editorRef.current.focus();
return;
}
const { onSubmit } = props;
onSubmit(editCode);
const { onSubmitForm } = props;
onSubmitForm(editCode);
}
// 控制台点击时 添加active属性
@ -102,7 +105,7 @@ const RightPaneCode = (props) => {
<Fragment>
<div className={'right_pane_code_wrap'}>
<div className={'code-title'}>
<span>ts.js</span>
{/* <span>ts.js</span> */}
<span>已保存</span>
<Icon className={'code-icon'} type="setting" onClick={handleShowDrawer}/>
</div>
@ -127,12 +130,11 @@ const RightPaneCode = (props) => {
{tabs}
</Tabs>
<div className="pane_control_opts">
<Button type="link" size="small" 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>
<Button ghost size="small" style={{ marginRight: '10px', color: '#28BD8B', borderColor: '#28BD8B' }}>调试代码</Button>
<Button ghost style={{ marginRight: '10px', color: '#28BD8B', borderColor: '#28BD8B' }}>调试代码</Button>
<Button
type="primary"
size="small"
type="primary"
onClick={handleSubmit}
>提交</Button>
</p>

@ -8,6 +8,7 @@
import './index.scss';
import React, { PureComponent } from 'react';
import { Icon } from 'antd';
import Editor from "@monaco-editor/react";
const tabCtx = (ctx, props) => (<p {...props}>{ctx}</p>);
const loadingCtx = (<span className={'ctx_loading'}><Icon className={'ctx_icon'} type="loading"/>加载中...</span>);
@ -17,21 +18,29 @@ const maps = {
// 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(ctx, { 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}` })
// 显示结果
final: (ctx, position) => tabCtx(ctx, { className: `tab_ctx_area tab_ctx_final pos_${position}` }),
// 显示自定义测试用例面板
// case: (ctx, position) =>
}
export default class InitTabCtx extends PureComponent {
handleEditorDidMount = () => {}
render () {
/**
* @param state 当前状态 default: 显示提示信息 init: 加载初始内容 loading: 加载中 loaded: 加载完成 final: 显示最终内容
* @param position: start | cetner | end
* @param testCase: 自定义测试用例
* @returns
*/
const { state, ctx, position = 'start' } = this.props;
const { state, ctx, position = 'start', testCase} = this.props;
return(
<React.Fragment>
{ maps[state](ctx, position) }

@ -0,0 +1,118 @@
.new_add_task_wrap,
.student_study_warp{
height: 100vh;
.task_header,
.student_study_header{
height: 65px;
background:rgba(34,34,34,1);
padding:0 30px;
}
.task_header{
display: flex;
align-items: center;
// justify-content: space-between;
.header_btn,
.header_title{
color: #fff;
}
.header_btn{
width: 88px;
}
.header_title{
flex: 1;
text-align: center;
}
}
.split-pane-area{
position: relative;
height: calc(100% - 65px);
.left_pane,
.right_pane{
height: 100%;
}
}
.student_study_header{
position: relative;
.avator_nicker,
.study_quit,
.study_name{
color: #fff;
line-height: 65px;
}
.avator_nicker,
.study_quit{
display: inline-block;
vertical-align: top;
}
.student_nicker{
margin-left: 20px;
}
.study_quit{
float: right;
}
.study_name{
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
text-align: center;
}
}
}
.Resizer {
background: #000;
opacity: 0.2;
z-index: 1;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-moz-background-clip: padding;
-webkit-background-clip: padding;
background-clip: padding-box;
}
.Resizer:hover {
-webkit-transition: all 2s ease;
transition: all 2s ease;
}
.Resizer.horizontal {
height: 11px;
margin: -5px 0;
border-top: 5px solid rgba(255, 255, 255, 0);
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
width: 100%;
}
.Resizer.horizontal:hover {
border-top: 5px solid rgba(0, 0, 0, 0.5);
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.vertical {
width: 11px;
margin: 0 -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize;
}
.Resizer.vertical:hover {
border-left: 5px solid rgba(0, 0, 0, 0.5);
border-right: 5px solid rgba(0, 0, 0, 0.5);
}
.Resizer.disabled {
cursor: not-allowed;
}
.Resizer.disabled:hover {
border-color: transparent;
}

@ -0,0 +1,59 @@
/*
* @Description: 学员学习
* @Author: tangjiang
* @Github:
* @Date: 2019-11-23 10:53:19
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-23 11:48:10
*/
import './index.scss';
import React from 'react';
import { connect } from 'react-redux';
import SplitPane from 'react-split-pane';
import LeftPane from './leftpane';
import RightPane from './rightpane';
import { Button, Avatar } from 'antd';
const StudentStudy = (props) => {
return (
<div className={'student_study_warp'}>
<div className={'student_study_header'}>
<div className={'avator_nicker'}>
<Avatar icon="user" />
<span className={'student_nicker'}>
我是昵称
</span>
</div>
<div className={'study_name'}>
<span>乘积最大序列</span>
</div>
<div className={'study_quit'}>
<Button>退出</Button>
</div>
</div>
<div className="split-pane-area">
<SplitPane split="vertical" minSize={200} maxSize={-200} defaultSize="50%">
<div className={'split-pane-left'}>
<LeftPane />
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane />
<div />
</SplitPane>
</SplitPane>
</div>
</div>
)
}
const mapStateToProps = (state) => ({});
const mapDispatchToProps = (dispatch) => ({});
export default connect(
mapStateToProps,
mapDispatchToProps
)(StudentStudy);

@ -0,0 +1 @@
@import '../split_pane_resizer.scss';

@ -0,0 +1,18 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-23 11:33:41
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-23 11:34:18
*/
import React from 'react';
const LeftPane = (props) => {
return (
<h2>左侧内容</h2>
);
}
export default LeftPane;

@ -0,0 +1,18 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-23 11:34:32
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-23 11:34:48
*/
import React from 'react';
const RightPane = (props) => {
return (
<h2>右侧内容</h2>
);
}
export default RightPane;

@ -6,7 +6,24 @@
* @Last Modified time: 2019-11-14 09:29:45
*/
const types = {
ADD_TODO: 'ADD_TODO'
ADD_TODO: 'ADD_TODO',
GET_OJ_LIST: 'GET_OJ_LIST', // JC 列表
SAVE_OJ_FORM_ID: 'SAVE_OJ_FORM_ID', // 保存OJ form表单信息
GET_OJ_BY_ID: 'GET_OJ_BY_ID', // 根据 id 号获取ojList中的数据
SAVE_OJ_FORM_CODE: 'SAVE_OJ_FORM_CODE', // 代码
VALIDATE_OJ_FORM: 'VALIDATE_OJ_FORM', // 验证表单
VALIDATE_OJ_NAME: 'VALIDATE_OJ_NAME', // 任务名称
VALIDATE_OJ_LANGUAGE: 'VALIDATE_OJ_LANGUAGE', // 编程语言
VALIDATE_OJ_DESCRIPTION: 'VALIDATE_OJ_DESCRIPTION', // 描述
VALIDATE_OJ_DIFFICULT: 'VALIDATE_OJ_DIFFICULT', // 难易度
VALIDATE_OJ_TIMELIMIT: 'VALIDATE_OJ_TIMELIMIT', // 时间限制
VALIDATE_OJ_CATEGORY: 'VALIDATE_OJ_CATEGORY', // 分类
VALIDATE_OJ_OPENORNOT: 'VALIDATE_OJ_OPENORNOT', // 公开程序
SAVE_OJ_FORM: 'SAVE_OJ_FORM', // 保存表单
ADD_TEST_CASE: 'ADD_TEST_CASE', // 添加测试用例
DELETE_TEST_CASE: 'DELETE_TEST_CASE', // 删除测试用例
SAVE_TEST_CASE: '保存测试用例', // 保存测试用例
CLEAR_JSFORM_STORE: 'CLEAR_JSFORM_STORE', // 清空测试用例
}
export default types;

@ -7,7 +7,39 @@
*/
import toggleTodo from './testAction.js';
import {getOJList} from './ojList';
import {
validateOjForm,
saveOjFormCode,
getOJFormById,
saveOJFormId,
clearOJFormStore,
validateOJName,
validateOjLanguage,
validateOjDescription,
validateOjDifficult,
validateOjTimeLimit,
validateOjCategory,
validateOpenOrNot,
addTestCase,
deleteTestCase
} from './ojForm';
export default {
toggleTodo
toggleTodo,
getOJList,
getOJFormById,
saveOJFormId,
clearOJFormStore,
validateOjForm,
saveOjFormCode,
validateOJName,
validateOjLanguage,
validateOjDescription,
validateOjDifficult,
validateOjTimeLimit,
validateOjCategory,
validateOpenOrNot,
addTestCase,
deleteTestCase
}

@ -0,0 +1,234 @@
/*
* @Description: 开发者社区编辑模块
* @Author: tangjiang
* @Github:
* @Date: 2019-11-20 16:35:46
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 20:07:04
*/
import types from './actionTypes';
import jcLabel from '../../constants';
import { fetchPostOjForm, fetchGetOjById } from '../../services/ojService';
// 表单字段映射
const maps = {
name: {
label: jcLabel['name'],
type: types.VALIDATE_OJ_NAME
},
language: {
label: jcLabel['language'],
type: types.VALIDATE_OJ_LANGUAGE
},
description: {
label: jcLabel['description'],
type: types.VALIDATE_OJ_DESCRIPTION
},
difficult: {
label: jcLabel['difficult'],
type: types.VALIDATE_OJ_DIFFICULT
},
timeLimit: {
label: jcLabel['timeLimit'],
type: types.VALIDATE_OJ_TIMELIMIT
},
category: {
label: jcLabel['category'],
type: types.VALIDATE_OJ_CATEGORY
},
openOrNot: {
label: jcLabel['openOrNot'],
type: types.VALIDATE_OJ_OPENORNOT
}
};
// 非空校验
const emptyValidate = (key, value) => {
if ([undefined, '', null].includes(value)) {
return {
[key]: {
validateStatus: 'error',
errMsg: `${maps[key].label}不能为空`
}
}
} else {
return {
[key]: {
validateStatus: '',
errMsg: ''
}
}
}
};
// 组装字段值及校验信息
const payloadInfo = (key, value, errMsg, validateInfo) => ({
ojForm: {
[key]: errMsg ? '' : value
},
ojFormValidate: {
[key]: validateInfo
}
});
// 表单提交验证
export const validateOjForm = () => {
return (dispatch, getState) => {
const {ojForm} = getState().ojFormReducer;
let keys = Object.keys(ojForm);
// 循环判断每个字段是否为空
let hasSuccess = true;
keys.forEach(key => {
const value = ojForm[key];
const validateResult = emptyValidate(key, value);
const errMsg = validateResult[key].errMsg;
if (errMsg) {
hasSuccess = false;
dispatch(
{
type: maps[key].type,
payload: payloadInfo(key, value, errMsg, validateResult[key])
}
)
}
});
// 验证成功后,调用提交接口
if (hasSuccess) {
console.log('表单保存的数据为: ', getState());
const {ojFormReducer} = getState();
const {code, score, ojForm, testCases} = ojFormReducer;
const {category, description, difficult, language, name, openOrNot, timeLimit} = ojForm;
const params = {
hack: {
name,
description,
difficult,
category,
'open_or_not': openOrNot,
'time_limit': timeLimit,
score
},
'hack_sets': testCases,
'hack_codes': {
code,
language
}
}
fetchPostOjForm(params).then(res => {
// TODO
}).catch(err => {
// TODO
});
// dispatch({type: types.VALIDATE_OJ_FORM});
}
}
};
// 保存提交的代码
export const saveOjFormCode = (value) => {
return {
type: types.SAVE_OJ_FORM_CODE,
payload: value
};
}
// 验证任务名称
export const validateOJName = (value) => {
const validate = emptyValidate('name', value)['name'];
const errMsg = validate.errMsg;
return {
type: types.VALIDATE_OJ_NAME,
payload: payloadInfo('name', value, errMsg, validate)
}
};
// 验证编程语言
export const validateOjLanguage = (value) => {
const validate = emptyValidate('language', value)['language'];
const errMsg = validate.errMsg;
return {
type: types.VALIDATE_OJ_LANGUAGE,
payload: payloadInfo('language', value, errMsg, validate)
}
};
// 验证描述
export const validateOjDescription = (value) => {
// createAction('description', value, types.VALIDATE_OJ_DESCRIPTION);
const validate = emptyValidate('description', value)['description'];
const errMsg = validate.errMsg;
return {
type: types.VALIDATE_OJ_DESCRIPTION,
payload: payloadInfo('description', value, errMsg, validate)
}
};
// 验证难易度
export const validateOjDifficult = (value) => {
// createAction('difficult', value, types.VALIDATE_OJ_DIFFICULT);
const validate = emptyValidate('difficult', value)['difficult'];
const errMsg = validate.errMsg;
return {
type: types.VALIDATE_OJ_DIFFICULT,
payload: payloadInfo('difficult', value, errMsg, validate)
}
};
// 验证时间限制
export const validateOjTimeLimit = (value) => {
// createAction('timeLimit', value, types.VALIDATE_OJ_TIMELIMIT);
const validate = emptyValidate('timeLimit', value)['timeLimit'];
const errMsg = validate.errMsg;
return {
type: types.VALIDATE_OJ_TIMELIMIT,
payload: payloadInfo('timeLimit', value, errMsg, validate)
}
};
// 验证分类
export const validateOjCategory = (value) => {
// createAction('category', value, types.VALIDATE_OJ_CATEGORY);
const validate = emptyValidate('category', value)['category'];
const errMsg = validate.errMsg;
return {
type: types.VALIDATE_OJ_CATEGORY,
payload: payloadInfo('category', value, errMsg, validate)
}
};
// 验证公开程序
export const validateOpenOrNot = (value) => {
const validate = emptyValidate('openOrNot', value)['openOrNot'];
const errMsg = validate.errMsg;
return {
type: types.VALIDATE_OJ_OPENORNOT,
payload: payloadInfo('openOrNot', value, errMsg, validate)
}
};
// 新增测试用例
export const addTestCase = (obj) => {
return {
type: types.ADD_TEST_CASE,
payload: obj
}
}
// 删除测试用例
export const deleteTestCase = (obj) => {
return {
type: types.DELETE_TEST_CASE,
payload: obj
}
}
// 更新id号编辑OJ
export const getOJFormById = (id) => {
return (dispatch) => {
fetchGetOjById(id).then(res => {
console.log('获取OJ表单信息成功: ', res);
})
}
}
// 保存表单 id 信息
export const saveOJFormId = (id) => {
return {
type: types.SAVE_OJ_FORM_ID,
payload: id
}
}
// 清空测试用例集合
export const clearOJFormStore = () => {
return {
type: types.CLEAR_JSFORM_STORE
}
}

@ -0,0 +1,23 @@
/*
* @Description: 开发者社区action
* @Author: tangjiang
* @Github:
* @Date: 2019-11-20 10:48:24
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 16:44:44
*/
import types from './actionTypes';
import { fetchOJList } from '../../services/ojService';
export const getOJList = (params) => {
return (dispatch) => {
fetchOJList(params).then((res) => {
console.log('获取列表信息成功: ', res);
const { data } = res;
dispatch({
type: types.GET_OJ_LIST,
payload: data
});
});
}
}

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

@ -0,0 +1,143 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-20 16:40:32
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 20:08:07
*/
import types from '../actions/actionTypes';
const init = {
ojForm: {
name: '', // 任务名称
language: '',
description: '',
difficult: 1,
category: 1,
openOrNot: 1,
timeLimit: ''
},
ojFormValidate: {
name: {
validateStatus: '',
errMsg: ''
},
language: {
validateStatus: '',
errMsg: ''
},
description: {
validateStatus: '',
errMsg: ''
},
difficult: {
validateStatus: '',
errMsg: ''
},
category: {
validateStatus: '',
errMsg: ''
},
openOrNot: {
validateStatus: '',
errMsg: ''
},
timeLimit: {
validateStatus: '',
errMsg: ''
}
},
testCases: [
// {
// input: "11 22",
// output: "33",
// position: 1, // 当前测试用例位置
// isAdd: true // 是否是新增
// }
], // 测试用例集合
position: 1, // TODO 每次加载信息时同步指定positio值
score: 200, // 分值: 选择难易度后自动计算分值 200 | 500 | 1000
code: '', // 提交的代码
identifier: '' // OJ表单id
}
const initialState = Object.assign({}, init);
const ojFormReducer = (state = initialState, action) => {
let ojFormValidate = {};
let ojForm = {};
if (action.payload) {
ojFormValidate = action.payload.ojFormValidate;
ojForm = action.payload.ojForm;
}
const returnState = (state, ojForm, ojFormValidate) => {
return {
...state,
ojFormValidate: Object.assign({}, state.ojFormValidate, ojFormValidate),
ojForm: Object.assign({}, state.ojForm, ojForm)
};
}
switch (action.type) {
case types.VALIDATE_OJ_FORM:
// 验证成功后,调用后台接口
return returnState(state, ojForm, ojFormValidate);
case types.SAVE_OJ_FORM_CODE:
return {
...state,
code: action.payload
}
case types.VALIDATE_OJ_NAME:
// 验证任务名称
return returnState(state, ojForm, ojFormValidate);
case types.VALIDATE_OJ_DESCRIPTION:
return returnState(state, ojForm, ojFormValidate);
case types.VALIDATE_OJ_LANGUAGE:
return returnState(state, ojForm, ojFormValidate);
case types.VALIDATE_OJ_DIFFICULT:
const difficult = action.payload.ojForm.difficult.trim();
if (action.payload.ojForm.difficult) {
state.score = scoreMaps[`${difficult}`];
}
return returnState(state, ojForm, ojFormValidate);
case types.VALIDATE_OJ_CATEGORY:
return returnState(state, ojForm, ojFormValidate);
case types.VALIDATE_OJ_OPENORNOT:
return returnState(state, ojForm, ojFormValidate);
case types.VALIDATE_OJ_TIMELIMIT:
return returnState(state, ojForm, ojFormValidate);
case types.ADD_TEST_CASE:
state.testCases.push(action.payload);
state.position++; // 位置递增
return {
...state
};
case types.DELETE_TEST_CASE:
const { position } = action.payload;
const { testCases } = state;
// 根据 position 去查找当前元素在数组中的位置
const index = testCases.findIndex((item) => item.position === position);
if (index > -1) {
testCases.splice(index, 1); // 删除当前元素
}
return {
...state
};
case types.SAVE_OJ_FORM_ID:
state.identifier = action.payload;
return {
...state
}
case types.CLEAR_JSFORM_STORE:
state = Object.assign({}, init);
return {
...state
}
default:
return state;
}
}
export default ojFormReducer;

@ -0,0 +1,29 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-21 22:17:03
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 09:17:59
*/
import types from '../actions/actionTypes';
const initialState = {
hacks_list: [],
top_data: {},
hacks_count: 0 // 总条数
};
const ojListReducer = (state = initialState, action) => {
switch (action.type) {
case types.GET_OJ_LIST:
return {
...state,
...action.payload
}
default:
return state;
}
}
export default ojListReducer;

@ -0,0 +1,36 @@
/*
* @Description: 开发者社区接口
* @Author: tangjiang
* @Github:
* @Date: 2019-11-20 10:55:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-11-22 16:48:35
*/
import axios from 'axios';
export async function fetchOJList (params) {
console.log('传递的参数: ', params);
const obj = {};
Object.keys(params).forEach(key => {
if (params[key]) {
obj[key] = params[key];
}
});
return axios.get('/problems.json', { params: obj });
}
// 提交
export async function fetchPostOjForm (params) {
const url = `/problems.json`;
return axios.post(url, params);
}
// 根据id号获取OJ信息
export async function fetchGetOjById (id) {
const url = `/problems/${id}/edit.json`;
return axios.get(url);
}
Loading…
Cancel
Save