init project

This commit is contained in:
Jasder
2020-03-09 00:40:16 +08:00
commit 2937b2a94d
6549 changed files with 7215173 additions and 0 deletions

View File

@@ -0,0 +1,589 @@
/*
* @Description: undefined
* @Author: tangjiang
* @Date: 2019-11-15 11:02:49
* @Last Modified by: tangjiang
* @Last Modified time: 2019-11-18 16:52:38
*/
import './index.scss';
import React from 'react';
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';
import CONST from '../../constants';
import { withRouter } from 'react-router';
import { toStore, CNotificationHOC } from 'educoder';
// import MyIcon from '../../common/components/MyIcon';
const {tagBackground, diffText} = CONST;
const { Search } = Input;
const { Option } = Select;
// import reqwest from 'reqwest';
/**
* 下拉菜单
*/
const maps = {
'categoryMenu': [
{
'key': '0',
'name': '全部',
'value': '0'
},
{
'key': '1',
'name': '程序设计基础',
'value': '1'
},
{
'key': '2',
'name': '数据结构与计算',
'value': '2'
}
],
'languageMenu': [
{
'key': 'C',
'name': 'C',
'value': 'C'
},
{
'key': 'C++',
'name': 'C++',
'value': 'C++'
},
{
'key': 'Python',
'name': 'Python',
'value': 'Python'
},
{
'key': 'Java',
'name': 'Java',
'value': 'Java'
}
],
'difficultMenu': [
{
'key': '1',
'name': '简单',
'value': '1'
},
{
'key': '2',
'name': '中等',
'value': '2'
},
{
'key': '3',
'name': '困难',
'value': '3'
}
],
'statusMenu': [
{
'key': '-1',
'name': '未做',
'value': '-1'
},
{
'key': '0',
'name': '未通过',
'value': '0'
},
{
'key': '1',
'name': '已通过',
'value': '1'
}
],
'come_fromMenu': [
{
'key': 'all',
'name': '全部',
'value': 'all'
},
{
'key': 'mine',
'name': '我创建的',
'value': 'mine'
}
]
};
const testMaps = {
category: {
1: '程序设计基础',
2: '数据结构与算法'
}
}
class DeveloperHome extends React.PureComponent {
/**
* 表格列
*/
options = {
title: '操作',
key: 'action',
// fixed: 'right',
width: 100,
render: (text, record) => (
<React.Fragment>
<Button
shape="circle"
type="primary"
icon="edit"
size="small"
onClick={() => this.handleClickEditor(record.identifier)}
>
{/* <Link to={`/problems/${record.identifier}/edit`}></Link> */}
</Button>
<Button
shape="circle"
type="danger"
icon="close"
size="small"
style={{ marginLeft: '10px', display: record.open_or_not ? 'none' : 'inline-block' }}
onClick={() => this.handleClickDelete(record)}
>
</Button>
</React.Fragment>
),
}
columns = [
{
title: '标题',
dataIndex: 'name',
render: (name, record) => <Button type="link" onClick={() => this.handleNameClick(record)} className={'oj_item_name'}>{name}</Button>
},
{
title: '分类',
dataIndex: 'category',
width: '20%',
align: 'center',
render: (category) => <span>{category ? testMaps['category'][+category] : '-'}</span>
},
{
title: '难度',
dataIndex: 'difficult',
align: 'center',
width: '15%',
render: (difficult) => {
if (difficult) {
return <Tag color={tagBackground[+difficult]}>{diffText[+difficult]}</Tag>
} else {
return '-';
}
}
},
{
title: '热度',
dataIndex: 'hack_user_lastest_codes_count',
sorter: true,
align: 'center',
width: '10%'
},
{
title: '通过率',
dataIndex: 'passed_rate',
sorter: true,
align:'right',
width: '10%',
render: val => <span>{`${val}%`}</span>
},
];
state = {
data: [],
loading: false,
searchParams: {
search: '', // 查询关键字
'come_from': '', // 来源
difficult: '', // 难易度
status: '', // 未做
category: '', // 分类
'sort_by': '', // 排序
'sort_direction': '', // 排序方向
page: 1, // 当前页数
limit: 10 // 每页显示条件
},
columns: this.columns,
searchInfo: []
};
componentDidMount() {
// 是否是我的,如果是我的 显示编辑按钮
const { isMySource } = this.props;
if (isMySource) {
this.handleFilterSearch({come_from: 'mine'});
let _columns = this.columns.concat([this.options]);
this.setState({
columns: _columns
});
} else {
this.fetchData();
}
const {hacks_count} = this.props.ojListReducer;
this.setState({
pagination: {
total: hacks_count
}
});
}
// 是否登录
isLogin = (url) => {
if(this.props.checkIfLogin()===false){
this.props.showLoginDialog()
return false;
}
return true;
}
// 新建
handleClickNew = () => {
if (this.isLogin()) {
// this.props.history.push('/problems/new');
window.location.href = '/problems/new'
}
// window.location.href = '/problems/new';
}
// 编辑
handleClickEditor = (identifier) => {
if (this.isLogin()) {
this.props.history.push(`/problems/${identifier}/edit`)
}
}
// 删除
handleClickDelete = (record) => {
const { deleteItem } = this.props;
this.props.confirm({
title: '提示',
content: `确定要删除${record.name}吗?`,
onOk () {
// 调用删除接口
// console.log(record.identifier);
deleteItem(record.identifier);
}
});
// Modal.confirm({
// title: '删除',
// content: `确定要删除${record.name}吗?`,
// okText: '确定',
// cancelText: '取消',
// onOk () {
// // 调用删除接口
// console.log(record.identifier);
// deleteItem(record.identifier);
// }
// });
}
// table条件变化时
handleTableChange = (pagination, filters, sorter) => {
const {field, order} = sorter;
const {current, pageSize} = pagination;
this.handleFilterSearch({
sort_by: field,
sort_direction: order === 'descend' ? 'desc' : 'asc',
page: current,
limit: pageSize
});
this.props.changePaginationInfo(pagination);
};
// 获取接口数据
fetchData = () => {
this.props.fetchOJList(this.state.searchParams);
};
/**
* 根据类型获取下拉菜单
* @param type 类型
* @param handleClick 处理函数
*/
getMenuItems = (type, handleClick) => {
return (
<Menu onClick={handleClick}>
{
maps[type].map((item) => {
return (
<Menu.Item key={item.key}>
{item.name}
</Menu.Item>
)
})
}
</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
}, () => {
if (this.isLogin()) {
this.fetchData();
}
});
}
// 添加显示搜索条件
addShowFilterCtx = (obj) => {
const {searchInfo} = this.state
const index = searchInfo.findIndex(item => item.type === obj.type);
let tempArr = [...searchInfo];
if (index > -1) {
tempArr[index] = obj;
} else {
tempArr.push(obj);
}
this.setState({
searchInfo: tempArr
});
}
/**
* 搜索输入框
* @param value 输入框值
*/
handleInputSearch = (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 = (item) => {
this.addShowFilterCtx({
type: 'category',
key: item.key
});
this.handleFilterSearch({category: +item.key === 0 ? '' : +item.key});
}
// 下拉语言
handleLanguageMenuClick = (item) => {
this.addShowFilterCtx({
type: 'language',
key: item.key
});
this.handleFilterSearch({language: item.key})
}
// 难度下拉
handleHardMenuClick = (item) => {
this.addShowFilterCtx({
type: 'difficult',
key: item.key
});
this.handleFilterSearch({difficult: +item.key});
}
// 状态下拉
handleSatusMenuClick = (item) => {
this.addShowFilterCtx({
type: 'status',
key: item.key
});
this.handleFilterSearch({status: +item.key});
}
// 来源下拉
handleOriginMenuClick = (item) => {
this.addShowFilterCtx({
type: 'come_from',
key: item.key
});
this.handleFilterSearch({come_from: item.key === 'all' ? '' : item.key});
if (item.key !== 'all') {
let _columns = this.columns.concat([this.options]);
this.setState({
columns: _columns
});
} else {
this.setState({
columns: this.columns
})
}
}
handleTagClose = (info) => {
this.handleFilterSearch({[info.type]: ''});
// 移除 searcInfo 中的数据
const { type } = info;
let tempArr = [...this.state.searchInfo];
const index = tempArr.findIndex(item => item.type === type);
if (index > -1) tempArr.splice(index, 1);
this.setState({
searchInfo: tempArr
});
if (info.type === 'come_from' && info.key === 'mine') {
this.setState({
columns: this.columns
});
}
}
// 点击name
handleNameClick = (record) => {
// console.log('name has click', record);
// 先调用start接口获取返回的 identifier, 再跳转到开启编辑
if (this.isLogin()) {
toStore('hack_identifier', record.identifier); // 保存当前编辑的id号
this.props.startProgramQuestion(record.identifier, this.props);
}
}
// if(this.props.checkIfLogin()===false){
// this.props.showLoginDialog()
// return
// }
render () {
// const { testReducer, handleClick } = this.props;
const {
ojListReducer: {hacks_list, top_data, hacks_count},
user,
pagination
} = this.props;
const {passed_count = 0, simple_count = 0, medium_count = 0, diff_count = 0} = top_data;
const { columns } = this.state;
// 渲染条件内容
const renderSearch = () => {
return this.state.searchInfo.map(info => {
let ctx = '';
const arrs = maps[`${info.type}Menu`];
arrs.forEach(item => {
if (item.key === info.key) ctx = item.name;
});
return (
<Tag
closable
className={'search_tag_style'}
key={info.type}
onClose={() => this.handleTagClose(info)}
>{ctx}</Tag>
)});
};
// console.log('=====>>>>>>>>>.', this.props);
const newBtnStyle = user && (user.admin || (user.is_teacher && user.professional_certification) || user.business)
? { display: 'block'}
: { display: 'none'};
return (
<div className="developer-list">
<div className="ant-spin-container">
<div className={'banner-wrap'}></div>
<div className="educontent">
<div className={'card-top'}>
<div className="search-params">
<p className={'save-question'}>已解决 <span className={''}>{passed_count}</span> / {hacks_count} </p>
<div className={'question-level'}>
<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 style={ newBtnStyle } type="primary" onClick={this.handleClickNew}>新建
{/* <Link to="/problems/new">新建</Link> */}
</Button>
</div>
</div>
<div className={'card-table'}>
<div className={'filter_ctx_area'}>
<div>
<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="bottomLeft" overlay={this.getMenuItems('languageMenu', this.handleLanguageMenuClick)}>
<span className={'dropdown-span'}>语言 <Icon type="down"/></span>
</Dropdown>
<Dropdown className={'dropdonw-style'} placement="bottomLeft" overlay={this.getMenuItems('difficultMenu', this.handleHardMenuClick)}>
<span className={'dropdown-span'}>难度 <Icon type="down"/></span>
</Dropdown>
<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="bottomLeft" overlay={this.getMenuItems('come_fromMenu', this.handleOriginMenuClick)}>
<span className={'dropdown-span'}>来源 <Icon type="down"/></span>
</Dropdown>
</div>
<div className={'choice_ctx'}>
{renderSearch()}
</div>
<Search
placeholder="输入标题进行搜索"
onChange={this.handleSearchChange}
onSearch={value => this.handleInputSearch(value)}
style={{ width: 320, float: 'right' }}
/>
</div>
<Card bordered={false} style={{ marginTop: '2px'}}>
<Table
columns={columns}
rowKey={record => Math.random()}
dataSource={hacks_list}
pagination={pagination}
onChange={this.handleTableChange}
/>
</Card>
</div>
</div>
</div>
</div>
);
}
}
/**
* @param {*} state store
* @param {*} ownProps DeveloperHome 中的 props
*/
const mapStateToProps = (state, ownProps) => {
const {
testReducer,
ojListReducer,
commonReducer
} = state;
const { pagination } = ojListReducer;
return {
testReducer,
ojListReducer,
isMySource: commonReducer.isMySource,
pagination
}
};
const mapDispatchToProps = (dispatch) => ({
handleClick: () => dispatch(actions.toggleTodo()),
fetchOJList: (params) => dispatch(actions.getOJList(params)),
changePaginationInfo: (obj) => dispatch(actions.changePaginationInfo(obj)),
startProgramQuestion: (id, props) => dispatch(actions.startProgramQuestion(id, props)),
deleteItem: (identifier) => dispatch(actions.deleteItem(identifier))
});
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(CNotificationHOC() (DeveloperHome)));
// export default DeveloperHome;

View File

@@ -0,0 +1,191 @@
/*
* @Description: 右侧代码块控制台
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 16:02:36
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-20 17:42:06
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
import { Tabs, Button, Icon, notification } 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 {
hack,
userCode,
inputValue,
loading,
submitLoading,
identifier,
excuteState,
// showOrHideControl,
commitTestRecordDetail,
changeLoadingState,
changeSubmitLoadingStatus,
changeShowOrHideControl,
// debuggerCode,
// startDebuggerCode, // 外部存入
onDebuggerCode,
// 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);
}
useEffect(() => {
setShowTextResult(props.showOrHideControl);
}, [props]);
// 显示/隐藏tab
const handleShowControl = () => {
setShowTextResult(!showTextResult);
changeShowOrHideControl(!showTextResult);
}
// 调试代码
const handleTestCode = (e) => {
if (!userCode) {
notification.warning({
message: '提示',
description: '代码块内容不能为空'
});
return;
}
// console.log(formRef.current.handleTestCodeFormSubmit);
// 调出控制台界面
setShowTextResult(true);
changeShowOrHideControl(true);
formRef.current.handleTestCodeFormSubmit(() => {
setDefaultActiveKey('2');
});
}
// 提交
const handleSubmit = (e) => {
e.preventDefault();
if (!userCode) {
notification.warning({
message: '提示',
description: '代码块内容不能为空'
});
return;
}
changeSubmitLoadingStatus(true);
onSubmitForm && onSubmitForm();
}
// 处理调度代码
const handleDebuggerCode = (values) => {
// 改变状态值
changeLoadingState(true);
// 调用代码保存接口, 成功后再调用调试接口
// updateCode(identifier, values, 'debug');
// 调用调试接口
// debuggerCode(identifier, values);
onDebuggerCode(values);
}
// icon-shangjiantou
return (
<div className="pane_control_area">
<div
className="pane_control_collapse"
onClick={handleShowControl}
style={{ top: showTextResult ? '-267px' : 0 }}
>
{/* <i className="iconfont icon-xiajiantou icon"></i> */}
<Icon type={ showTextResult ? "down" : "up" } />
</div>
<Tabs
className={classNames}
activeKey={defaultActiveKey}
tabBarStyle={{ backgroundColor: 'rgba(18,28,36,1)', 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={commitTestRecordDetail}
/>
</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, showOrHideControl } = commonReducer;
const { commitTestRecordDetail, hack, userCode } = ojForUserReducer;
return {
hack,
userCode,
loading,
submitLoading,
excuteState,
showOrHideControl,
// identifier: user_program_identifier,
commitTestRecordDetail // 提交详情
};
};
// changeSubmitLoadingStatus
const mapDispatchToProps = (dispatch) => ({
changeShowOrHideControl: (flag) => dispatch(actions.changeShowOrHideControl(flag)),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag)),
changeSubmitLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag)),
debuggerCode: (identifier, values) => dispatch(actions.debuggerCode(identifier, values)),
// inputValue 输入值
updateCode: (identifier, inputValue, type) => dispatch(actions.updateCode(identifier, inputValue, type))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ControlSetting);

View File

@@ -0,0 +1,146 @@
.pane_control_area{
position: absolute;
bottom: 0;
width: 100%;
.pane_control_collapse{
position: absolute;
width: 55px;
height: 27px;
background:rgba(42,58,79,1);
border-bottom-left-radius: 55px;
border-bottom-right-radius: 55px;
left: 50%;
margin-left: -27px;
text-align: center;
color: #fff;
line-height: 27px;
opacity: 0.5;
transition: opacity .3s;
z-index: 10;
.icon{
position: relative;
top: 0px;
}
&:hover{
opacity: .8;
}
}
// background: red;
// background:rgba(30,30,30,1);
// 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;
}
.ant-tabs-nav .ant-tabs-tab{
padding: 12px 0px;
}
}
.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: 20px;
padding-left: 5px;
background: rgba(18,28,36,1);
// background:rgba(48,48,48,1);
}
.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;
}
}
.ant-drawer-body{
height: calc(100vh - 120px);
overflow-y: auto;
}
.ant-drawer-content{
top: 120px;
}
}
@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;
}
}

View File

@@ -0,0 +1,88 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-03 15:20:55
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-27 22:35:14
*/
import './index.scss';
import React from 'react';
import MonacoEditor from '@monaco-editor/react';
function ErrorResult (props) {
const { detail, language } = props;
const renderError = (detail = {}) => {
const {
status,
// error_line,
error_msg,
expected_output,
input,
output,
execute_time,
// execute_memory
} = detail;
// 根据状态渲染不同的错误信息
let result = null;
switch (status) {
case -1:
result = (
<div className={'error_result_wrap error_result_txt'}>
<p>输入: [{input}]</p>
<p>输出: [{output}]</p>
<p>预期: [{expected_output}]</p>
</div>
);
break;
case 2: // 评测超时
result = (
<div className={'error_result_wrap error_result_txt'}>
<p>执行超时限制时限: {`${execute_time}s`}</p>
</div>
);
break;
case 3: // 创建pod失败
result = (
<div className={'error_result_wrap'}>
<p>系统繁忙请稍后重试</p>
</div>
);
break;
case 4: // 编译失败
result = (
<div className={'error_result_wrap error_result_code'}>
{/* <p>{error_msg}</p> */}
<MonacoEditor
height="100%"
width="100%"
language={(language && language.toLowerCase()) || 'c'}
value={error_msg}
theme="dark"
/>
</div>
);
break;
case 5: // 执行失败
result = (
<div className={'error_result_wrap'}>
<p>执行出错信息: </p>
<p>最后执行的输入: {input}</p>
</div>
);
break;
default:
return result;
}
return result;
}
return (
<React.Fragment>
{renderError(detail)}
</React.Fragment>
);
}
export default ErrorResult;

View File

@@ -0,0 +1,11 @@
.error_result_wrap{
&.error_result_txt{
padding: 20px 30px;
}
&.error_result_code{
height: 150px;
.error_result_code_txt{
line-height: 32px;
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* @Description: 执行结果
* @Author: tangjiang
* @Github:
* @Date: 2019-11-28 08:44:54
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 08:51:21
*/
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;
// console.log('执行状态: ======', excuteState);
// 指定渲染初始, 加载中, 加载完成页面内容
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 renderError = () => (
<div className={'excute_result_area excute_flex_center'}>
<span className={'loaded_ctx'}>
<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>
)
}
// console.log('执行结果====》》》》', status);
const excuteCtx = (state) => {
if (state === 0) {
return (
<React.Fragment>
<p className={'result_info_style'}>输入: {input}</p>
<p className={'result_info_style'}>输出: {output}</p>
<p className={'result_info_style'}>执行用时: {`${execute_time}s`}</p>
</React.Fragment>
);
} else if (state === 4){
return (
<p className={'result_info_style'}>
{/* 系统繁忙,请稍后重试 */}
{error_msg}
</p>
)
} else if (state === 3) {
return (
<p className={'result_info_style'}>
系统繁忙请稍后重试
</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(() => {
// console.log('执行状态====》》》》', excuteState);
if ('loading' === excuteState) {
setRenderCtx(() => (renderLoading));
} else if ('loaded' === excuteState) {
setRenderCtx(() => (readerLoaded));
} else if ('finish' === excuteState) {
setRenderCtx(() => (renderFinish));
} else if ('error' === excuteState) {
setRenderCtx(() => (renderError))
}
}, [excuteState]);
// 提交详情变化时
useEffect(() => {
// console.log('提交记录详情=====>>>>>', excuteDetail);
setCodeResult(excuteDetail);
}, [excuteDetail]);
return (
<React.Fragment>
{renderCtx()}
</React.Fragment>
)
}
export default ExecResult;

View File

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

View File

@@ -0,0 +1,100 @@
/*
* @Description: 自定义测试化用例
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 19:46:14
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 20:07:35
*/
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;
// console.log('default value', inputValue);
useImperativeHandle(ref, () => ({
handleTestCodeFormSubmit: (cb) => {
// console.log('父组件调用我啦~~~~~~~~~');
_handleTestCodeFormSubmit(cb);
}
}));
// useEffect(() => {
// console.log('初始值: ========', props);
// }, [props]);
// 渲染文本提示信息
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'}
>
{
getFieldDecorator('input', {
rules: [
{ required: true, message: '输入值不能为空'}
],
initialValue: inputValue
})(<TextArea
className="input_textarea_style"
rows={8}
placeholder="请填写测试用例的输入值,点击“调试代码”"
/>)
}
</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));

View File

@@ -0,0 +1,58 @@
.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 10px;
color: #fff;
}
.flex_r{
padding: 0 20px 0 10px;
}
.input_textarea_style{
// background:rgba(30,30,30,1) !important;
background:rgba(7,15,25,1) !important;
color: #fff;
border-color: transparent;
outline: none;
}
}

View File

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

View File

@@ -0,0 +1,194 @@
/*
* @Description: 知识点
* @Author: tangjiang
* @Github:
* @Date: 2019-12-30 13:51:19
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-07 15:46:24
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
import { Select, notification, Modal, Form, Input, Button } from 'antd';
const { Option } = Select;
function KnowLedge (props) {
const {
options = [], // 下拉选项
values = [], // 已选择的下拉项
onChange, // 获取选择的值
form,
showAdd, // 显示新增图标
addKnowledge // 调用新增知识点接口
} = props;
useEffect(() => {
const _options = [];
const _selects = [];
options.forEach(opt => {
if (!values.includes(opt.id)) {
_options.push(opt);
} else {
_selects.push(opt);
}
});
setSelectOptions(_options || []);
setSelectValue(_selects || []);
}, [props]);
// 显示的下拉项
const [selectOptions, setSelectOptions] = useState(options);
// 已选择的下拉项
const [selectValue, setSelectValue] = useState([]);
const [visible, setVisible] = useState(false);
//
const [value] = useState([]);
const { getFieldDecorator } = form;
const FormItem = Form.Item;
// 渲染下拉选项
const renderOptions = (options = []) => {
return options.map((opt, i) => (
<Option key={`opt_${i}`} value={`${opt.id}`}>{opt.name}</Option>
));
}
// 过滤下拉列表项
const handleSelectChange = (value) => {
// value = +value.join('');
value = +value;
const tempArr = [...selectValue];
const _result = selectOptions.filter(item => {
if (item.id === value && tempArr.findIndex(t => t.id === value) === -1) {
tempArr.push(item);
}
return item.id !== value;
});
if (tempArr.length > 5) {
notification.warning({
message: '提示',
description: '知识点不能超过5个'
});
return;
}
setSelectValue(tempArr);
setSelectOptions(_result);
// 将选择值返回
onChange && onChange(tempArr);
}
// 删除
const handleRemoveResult = (item) => {
// console.log('点击了删除按钮===>>>>', item);
// 将删除的值重新加入到列表中
const tempOptions = [...selectOptions];
const tempValue = selectValue.filter(t => t.id !== item.id);
// console.log(selectValue);
tempOptions.push(item);
setSelectOptions(tempOptions);
setSelectValue(tempValue);
// 将选择值返回
onChange && onChange(tempValue);
}
// 渲染下拉结果
const renderResult = (arrs) => {
return arrs.map((item) => (
<span className="knowledge-item" key={`item_${item.name}`}>
{item.name}
<span
onClick={() => handleRemoveResult(item)}
className="iconfont icon-roundclose knowledge-close"
></span>
</span>
));
}
// 渲染下拉列表
const renderSelect = (options = []) => {
// console.log('+++++', options);
// setSelectValue(_selects);
return (
<Select
value={value}
// mode="tags"
placeholder="请选择"
style={{ width: '100%' }}
onChange={handleSelectChange}
>
{renderOptions(options)}
</Select>
)
}
// 添加知识点
const handleAddKnowledge = () => {
setVisible(true);
};
const handleResetForm = () => {
form.resetFields();
setVisible(false);
}
const handleSubmitForm = (e) => {
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {
if (err) {
return;
}
setVisible(false);
form.resetFields();
// console.log(values);
addKnowledge && addKnowledge(values);
})
}
const _styles = {
display: showAdd ? 'inline-block' : 'none'
};
return (
<React.Fragment>
<div className="knowledge-select-area">
{ renderSelect(selectOptions) }
{/* 渲染下拉选择项 */}
<div className="knowledge-result">
<i
style={_styles}
className="iconfont icon-roundaddfill icon-add-knowledge"
onClick={handleAddKnowledge}
></i>
{ renderResult(selectValue) }
</div>
</div>
<Modal
closable={false}
title="新增知识点"
visible={visible}
footer={null}
>
<Form className="knowledge-form">
<FormItem>
{
getFieldDecorator('name', {
rules: [{
required: true, message: '知识点名称不能为空'
}]
})(
<Input />
)
}
</FormItem>
<FormItem style={{ textAlign: 'center' }}>
<Button style={{ marginRight: '20px' }} onClick={handleResetForm}>取消</Button>
<Button type="primary" onClick={handleSubmitForm}>确定</Button>
</FormItem>
</Form>
</Modal>
</React.Fragment>
);
}
export default Form.create()(KnowLedge);

View File

@@ -0,0 +1,56 @@
.knowledge-select-area{
.ant-select-selection__rendered{
margin-bottom: 0 !important;
}
.ant-select-search--inline{
margin-left: 5px;
margin-top: 2px;
}
.knowledge-result{
display: flex;
flex-direction: row;
flex-wrap: wrap;
// margin-top: 15px;
.knowledge-item{
position: relative;
border: 1px solid #DDDDDD;
border-radius: 3px;
padding: 10px;
background: #fff;
margin-right: 10px;
margin-top: 10px;
// margin-bottom: 10px;
.knowledge-close{
display: none;
position: absolute;
right: -10px;
top: -10px;
background-color: rgba(250,250,250,1);
cursor: pointer;
}
&:hover{
.knowledge-close{
display: block;
}
}
}
.icon-add-knowledge{
line-height: 36px;
margin-top: 10px;
margin-right: 10px;
// cursor: ;
color: rgb(78, 188, 126)
}
}
}
.knowledge-form{
.ant-form-explain{
padding: 0;
}
}

View File

@@ -0,0 +1,108 @@
/*
* @Description: 编辑器侧边栏设置信息
* @Author: tangjiang
* @Github:
* @Date: 2019-11-25 17:50:33
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-27 10:36:54
*/
import React, { useState } from 'react';
import { fromStore, toStore } from 'educoder';
// import { Icon } from 'antd';
// import { Select } from 'antd';
// const { Option } = Select;
const SettingDrawer = (props) => {
/**
* title: '', // 一级标题
* type: '', // 类型: 目录 select 和 文本
* content: [] // 显示的内容 { text: '' , value: string | [{ key: 1, value: '', text: '' }] }
*/
const [fontSize, setFontSize] = useState(() => {
return +fromStore('oj_fontSize') || 14;
});
const [theme, setTheme] = useState(() => {
return fromStore('oj_theme') || 'dark';
});
const {title, type = 'label', content = [] } = props;
// 字体改变时, 方法全名: handleChangeXX, XX 为指定的类型;
const {
onChangeFontSize,
onChangeTheme
} = props;
const handleChangeFont = (value) => {
setFontSize(value);
toStore('oj_fontSize', value);
onChangeFontSize && onChangeFontSize(value);
}
// 风格改变时
const handleChangeStyle = (value) => {
setTheme(value);
toStore('oj_theme', value);
onChangeTheme && onChangeTheme(value);
}
const handleSelectChange = (e, type) => {
const value = e.target.value;
if (type === 'font') {
handleChangeFont(value);
}
if (type === 'style') {
handleChangeStyle(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)) {
const defaultValue = ctx.type === 'font' ? fontSize : theme;
// console.log('++', defaultValue);
if (type === 'select') {
const child = ctx.value.map((opt, i) => {
return (
<option
key={`key_${i}` || `${opt.value}`}
value={opt.value}
>
{opt.text}
</option>
)});
renderResult = (
<div className={'setting_desc'} key={`sel_${index}`}>
<span className={'flex_item'}>{ctx.text}</span>
<select defaultValue={defaultValue} style={{ width: '100px'}} onChange={(e) => handleSelectChange(e, ctx.type)}>
{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;

View File

@@ -0,0 +1,36 @@
/*
* @Description: 显示 文字 + number 标签类型
* @Author: tangjiang
* @Date: 2019-11-15 10:41:06
* @Last Modified by: tangjiang
* @Last Modified time: 2019-11-15 17:15:27
*/
import './index.scss';
import React, { PureComponent } from 'react';
const numberal = require('numeral');
export default class MultipTags extends PureComponent {
render () {
const { type = 'primary', text, numb, ...props} = this.props;
if (typeof numb !== 'number' && typeof numb !== 'string') {
throw new Error('输入的numb必须为数字或数字类型字符串.');
}
let result = Number(numb) >= 1000
? numberal(Number(numb)).format('0.0a')
: Number(numb);
return (
<div className={'mul-tag-wrap'} {...props}>
<span className={`tag-txt ${type}`}>
{ text }
</span>
<span className={`tag-numb ${type}`}>
{ result }
</span>
</div>
)
}
}

View File

@@ -0,0 +1,79 @@
.mul-tag-wrap{
display: inline-block;
vertical-align: middle;
.tag-txt, .tag-numb{
display: inline-block;
vertical-align: middle;
// line-height: 20px;
// height: 20px;
box-sizing: border-box;
font-size: 12px;
text-align: center;
height: 24px;
padding: 0 10px;
}
.tag-txt,
.tag-numb{
border: 1px solid transparent;
&.primary{
// background: #28BD8B;
border-color: #28BD8B;
color: #28BD8B;
}
&.warning{
// background: #FF9802;
border-color: #FF9802;
color: #FF9802;
}
&.success{
// background: #52c41a;
border-color: #28BD8B;
color: #28BD8B;
}
&.error{
// background: #FF5555;
border-color: #FF5555;
color: #FF5555;
}
}
.tag-txt{
position: relative;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
// color: #fff;
border-right: none;
&::before {
position: absolute;
content: '';
right: 0;
top: 5px;
bottom:5px;
border-right: 1px solid transparent;
}
&.primary::before{
border-right-color: #28BD8B;
}
&.warning::before{
border-right-color: #FF9802;
}
&.success::before{
border-right-color: #28BD8B;
}
&.error::before{
border-right-color: #FF5555;
}
}
.tag-numb{
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
border-left-color: transparent;
margin-left: -1px;
min-width: 40px;
border-left: none;
}
}

View File

@@ -0,0 +1,223 @@
/*
* @Description: 抽取代码编辑器
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 15:02:52
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-02 13:59:38
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
import { Drawer, Tooltip, Badge } from 'antd';
import { fromStore, CNotificationHOC } from 'educoder';
import { connect } from 'react-redux';
import MonacoEditor from '@monaco-editor/react';
import SettingDrawer from '../../components/monacoSetting';
import CONST from '../../../../constants';
import MyIcon from '../../../../common/components/MyIcon';
// import actions from '../../../../redux/actions';
const { fontSetting, opacitySetting } = CONST;
const maps = {
'c': 'main.c',
'c++': 'main.cc',
'java': 'main.java',
'python': 'main.py'
};
function MyMonacoEditor (props, ref) {
const {
code,
notice,
language,
identifier,
hadCodeUpdate,
showOrHideControl,
// saveUserInputCode,
onCodeChange,
onRestoreInitialCode,
onUpdateNotice
} = props;
const [showDrawer, setShowDrawer] = useState(false); // 控制配置滑框
// const [editCode, setEditCode] = useState('');
// const [curLang, setCurLang] = useState('C');
const [fontSize, setFontSize] = useState(() => { // 字体
return +fromStore('oj_fontSize') || 14;
});
const [theme, setTheme] = useState(() => { // 主题 theme
return fromStore('oj_theme') || 'dark';
});
const [ height, setHeight ] = useState('calc(100% - 56px)');
const editorRef = useRef(null);
// useEffect(() => {
// setEditCode(props.code || '');
// }, [props]);
useEffect(() => {
setHeight(showOrHideControl ? 'calc(100% - 378px)' : 'calc(100% - 56px)');
}, [showOrHideControl]);
// 控制侧边栏设置的显示
const handleShowDrawer = () => {
setShowDrawer(true);
}
// 关闭设置
const handleDrawerClose = () => {
setShowDrawer(false);
}
// 侧边栏改变字体大小
const handleChangeFontSize = (value) => {
setFontSize(value);
}
// 改变主题
const handleChangeTheme = (value) => {
setTheme(value);
}
// 文本框内容变化时,记录文本框内容
const handleEditorChange = (origin, monaco) => {
editorRef.current = monaco; // 获取当前monaco实例
// setEditCode(origin); // 保存编辑器初始值
editorRef.current.onDidChangeModelContent(e => { // 监听编辑器内容的变化
// TODO 需要优化 节流
const val = editorRef.current.getValue();
// setEditCode(val);
// console.log('编辑器代码====>>>>', val);
onCodeChange(val);
// 值一变化保存当前代码值
// saveUserInputCode(val);
});
}
// 配置编辑器属性
const editorOptions = {
selectOnLineNumbers: true,
automaticLayout: true,
fontSize: `${fontSize}px`
}
// 恢复初始代码
const handleRestoreCode = () => {
props.confirm({
title: '提示',
content: '确定要恢复代码吗?',
onOk () {
onRestoreInitialCode && onRestoreInitialCode();
}
})
// Modal.confirm({
// content: '确定要恢复代码吗?',
// okText: '确定',
// cancelText: '取消',
// onOk () {
// onRestoreInitialCode && onRestoreInitialCode();
// }
// })
}
const handleUpdateNotice = () => {
if (props.notice) {
onUpdateNotice && onUpdateNotice();
}
}
// const renderRestore = identifier ? (
// <MyIcon type="iconzaicizairu" />
// ) : '';
// lex_has_save ${hadCodeUpdate} ? : ''
const _classnames = hadCodeUpdate ? `flex_strict flex_has_save` : 'flex_strict';
return (
<React.Fragment>
<div className={"monaco_editor_area"}>
<div className="code_title">
{/* 未保存时 ? '学员初始代码文件' : main.x */}
<span className='flex_strict' style={{ color: '#ddd'}}>{identifier ? language ? maps[language.toLowerCase()] : '' : '学员初始代码文件'}</span>
<span className={_classnames}>{hadCodeUpdate ? '已保存' : ''}</span>
{/* <Tooltip
style={{ background: 'gold' }}
className="tooltip_style"
title="通知"
placement="bottom"
> */}
<Tooltip
placement="bottom"
title="通知"
>
<Badge
className="flex_normal"
style={{ color: '#666'}}
dot={notice}
onClick={handleUpdateNotice}
>
{/* <Icon type="bell" /> */}
<MyIcon type="iconxiaoxi1" style={{fontSize: '18px'}}/>
</Badge>
</Tooltip>
<Tooltip
placement="bottom"
title="恢复"
>
<MyIcon
className="flex_normal"
onClick={handleRestoreCode}
type="iconzaicizairu"
style={{ display: identifier ? 'inline-block' : 'none', fontSize: '18px'}}
/>
{/* <span onClick={handleRestoreCode} className="flex_normal" style={{ display: identifier ? 'inline-block' : 'none'}}>{renderRestore}</span> */}
</Tooltip>
<Tooltip
placement="bottom"
title="设置"
>
<MyIcon className='code-icon' type="iconshezhi" onClick={handleShowDrawer} style={{fontSize: '18px'}}/>
</Tooltip>
</div>
<MonacoEditor
height={height}
width="100%"
language={language && language.toLowerCase()}
value={code || ''}
options={editorOptions}
theme={theme} // dark || light
editorDidMount={handleEditorChange}
/>
</div>
<Drawer
className={'setting_drawer'}
placement="right"
onClose={handleDrawerClose}
visible={showDrawer}
>
<SettingDrawer
{...fontSetting}
onChangeFontSize={handleChangeFontSize}
onChangeTheme={handleChangeTheme}
/>
<SettingDrawer {...opacitySetting}/>
</Drawer>
</React.Fragment>
)
};
const mapStateToProps = (state) => {
const { showOrHideControl } = state.commonReducer;
return {
showOrHideControl
}
};
// const mapDispatchToProps = (dispatch) => ({
// // saveUserInputCode: (code) => dispatch(actions.saveUserInputCode(code)),
// });
// MyMonacoEditor = React.forwardRef(MyMonacoEditor);
export default connect(
mapStateToProps,
// mapDispatchToProps
)(CNotificationHOC() (MyMonacoEditor));

View File

@@ -0,0 +1,96 @@
.monaco_editor_area{
height: 100%;
background-color: rgba(7,15,25,1);
.code_title{
display: flex;
align-items: center;
background-color: rgba(18,28,36,1);
color: #fff;
height: 56px;
padding: 0 20px;
.flex_strict{
flex: 1;
}
.flex_normal{
color: #E51C24;
cursor: pointer;
margin-right: 20px;
}
.code-icon{
cursor: pointer;
}
.flex_strict,
.flex_normal,
.code-icon{
color: #666;
}
}
// .margin,
// .margin-view-overlays,
// .current-line{
// width: 40px !important;
// }
// .monaco-editor .margin-view-overlays .line-numbers{
// text-align: center;
// }
// .monaco-scrollable-element{
// left: 40px !important;
// }
}
.setting_drawer{
.ant-drawer-close{
color: #ffffff;
}
.ant-drawer-content{
top: 120px;
bottom: 56px;
height: calc(100vh - 176px);
// background: #333333;
background: rgba(7,15,25,1);
color: #fff;
.setting_h2{
color: #fff;
}
select{
color: #fff;
background: #222222;
height: 24px;
// line-height: 24px;
margin-top: 4px;
}
select option{
background: gold;
color: #fff;
}
}
}
.flex_has_save{
// animation: blink 3s line 3;
animation-name: blink;
animation-duration: .4s;
animation-iteration-count: 3;
}
// .monaco-editor, .monaco-editor-background, .monaco-editor .inputarea.ime-input,
// .monaco-editor .margin,
// .minimap slider-mouseover,
// .minimap-decorations-layer{
// background:rgba(3,19,40,1) !important;
// }
@keyframes blink{
50% {
color: #fff;
}
}
.monaco-editor, .monaco-editor-background, .monaco-editor .inputarea.ime-input,
.monaco-editor .margin,
.minimap .minimap-decorations-layer{
background-color: transparent !important;
}

View File

@@ -0,0 +1,48 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-27 19:18:09
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-27 19:19:23
*/
import React, { useState } from 'react';
import Editor from "@monaco-editor/react";
function App() {
const [theme, setTheme] = useState("light");
const [language, setLanguage] = useState("javascript");
const [isEditorReady, setIsEditorReady] = useState(false);
function handleEditorDidMount() {
setIsEditorReady(true);
}
function toggleTheme() {
setTheme(theme === "light" ? "dark" : "light");
}
function toggleLanguage() {
setLanguage(language === "javascript" ? "python" : "javascript");
}
return (
<React.Fragment>
<LinkToRepo />
<button onClick={toggleTheme} disabled={!isEditorReady}>
Toggle theme
</button>
<button onClick={toggleLanguage} disabled={!isEditorReady}>
Toggle language
</button>
<Editor
height="calc(100% - 19px)" // By default, it fully fits with its parent
theme={theme}
language={language}
value={'c'}
editorDidMount={handleEditorDidMount}
/>
</React.Fragment>
);
}

View File

@@ -0,0 +1,81 @@
/*
* @Description: 文字 | 图标 + 数字样式
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 10:58:37
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-25 10:02:03
*/
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,
className,
theme = 'outlined'
} = props;
// console.log('style=====>>>>>>', style);
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 = (className, theme) => {
if (type === 'icon') { // 图标加文字时
const _className = `text_number_area text_icon_numb flex_${position} ${className}`;
const _classIcon = `iconfont icon-${text} numb_icon`;
return (
<div className={_className}>
{/* <Icon
theme={theme}
type={text}
className={'numb_icon'}
></Icon> */}
<span
className={_classIcon}
onClick={handleIconClick}>
</span>
{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(className, theme)}
</React.Fragment>
);
}
export default TextNumber;

View File

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

View File

@@ -0,0 +1,28 @@
/*
* @Description: 用户头像及昵称
* @Author: tangjiang
* @Github:
* @Date: 2019-12-09 17:11:28
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-09 17:36:55
*/
import './index.scss';
import React from 'react';
import { getImageUrl } from 'educoder'
function UserInfo (props) {
const {image_url, name} = props.userInfo;
return (
<div className={'avator_nicker'}>
<img style={{ display: image_url ? 'inline-block' : 'none'}} alt="用户头像" className={'student_img'} src={getImageUrl(`images/${image_url}` || 'images/educoder/headNavLogo.png?1526520218')} />
<span className={'student_nicker'}>
{name || ''}
</span>
</div>
);
}
export default UserInfo
export {
UserInfo
};

View File

@@ -0,0 +1,26 @@
.avator_nicker{
position: absolute;
color: #fff;
line-height: 65px;
left: 20px;
// height: 65px;
.student_img,
.student_nicker{
display: inline-block;
vertical-align: top;
}
.student_nicker{
margin-left: 10px;
}
.student_img{
width: 30px;
height: 30px;
border-radius: 50%;
margin-top: 15px;
}
}

View File

@@ -0,0 +1,19 @@
/*
* @Description: 开发者社区入口文件此处提供全局store并且此处Provier只能有一个子无互
* @Author: tangjiang
* @Date: 2019-11-13 20:14:04
* @Last Modified by: tangjiang
* @Last Modified time: 2019-11-15 20:43:27
*/
import React from 'react';
import { TPMIndexHOC } from '../tpm/TPMIndexHOC';
import { SnackbarHOC } from 'educoder';
import DeveloperHome from './DeveloperHome';
const App = (props) => {
return (
<DeveloperHome {...props}/>
);
}
export default SnackbarHOC()(TPMIndexHOC(App));

View File

@@ -0,0 +1,88 @@
.banner-wrap{
width: 100%;
height: 300px;
// background-image: url(/static/media/path.e39ba7de.png);
// background: #000a4f url(../../images/oj//oj_banner.jpg) none center;
// background-color: #000a4f;
// /* background-size: cover; */
// background-position: center;
// background-repeat: no-repeat;
background: rgb(0, 1, 35) url(../../images/oj/oj_banner.jpg) no-repeat center;
background-size: cover;
}
.developer-list{
// overflow: hidden;
.ant-spin-container{
padding-bottom: 100px;
}
.card-top {
border-radius:4px;
background:rgba(255,255,255,1);
height:56px;
padding: 0 30px;
margin-top: 20px;
.search-params{
display: flex;
align-items: center;
height: 100%;
}
.save-question{
width: 200px;
}
// .flex-end{
// // float: right;
// }
.question-level{
flex: 1;
}
}
.card-table{
margin-top: 10px;
.filter_ctx_area{
display: flex;
justify-content: space-between;
padding: 10px 30px;
background: #fff;
align-items: center;
}
.choice_ctx{
flex: 1;
}
.ant-card-body{
padding: 10px 30px;
// width: 100%;
}
.dropdown-span{
position: relative;
top: 2px;
}
.dropdonw-style{
margin-right: 50px;
cursor: pointer;
.dropdown-span{
cursor: pointer;
margin-right: 10px;
}
}
}
.search_tag_style{
background: rgb(82, 196, 26);
color: #fff;
.anticon-close{
color: #fff;
}
}
.oj_item_name{
flex-wrap: wrap;
color: #459be5;
cursor: pointer;
max-width: 510px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
}
}

View File

@@ -0,0 +1,287 @@
/*
* @Description: 新建或编辑任务
* @Author: tangjiang
* @Date: 2019-11-15 16:38:34
* @Last Modified by: tangjiang
* @Last Modified time: 2019-11-19 23:23:41
*/
import './index.scss';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import SplitPane from 'react-split-pane';// import { Form } from 'antd';
import { Button } from 'antd';
import LeftPane from './leftpane';
import RightPane from './rightpane';
import { withRouter } from 'react-router';
import { toStore, CNotificationHOC } from 'educoder';
import UserInfo from '../components/userInfo';
// import RightPane from './rightpane/index';
import actions from '../../../redux/actions';
// import {ModalConfirm} from '../../../common/components/ModalConfirm';
const NewOrEditTask = (props) => {
const {
publishLoading,
handlePublish,
// testCases = [],
// ojTestCaseValidate = [],
identifier,
isPublish,
userInfo,
submitLoading,
changeSubmitLoadingStatus,
changePublishLoadingStatus,
startProgramQuestion,
getUserInfoForNew,
handleCancelPublish,
validateOjForm,
getQuestion
// updateTestAndValidate,
} = props;
// 表单提交
const handleSubmitForm = () => {
// 改变loading状态
changeSubmitLoadingStatus(true);
// 调用输入表单验证功能
if (props.identifier) {
props.handleUpdateOjForm(props);
} else {
props.handleFormSubmit(props); // 提交表单
}
};
useEffect(() => {
// 获取用户信息
getUserInfoForNew();
// 获取课程列表
getQuestion({
source: 'question'
});
// 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 () => {}
}, []);
// 模拟挑战
const imitationChallenge = () => {
// 先调用保存, 再调用 start 接口, 成功后跳转到模拟页面
// identifier && startProgramQuestion(identifier, props);
identifier && validateOjForm(props, 'challenge', () => {
startProgramQuestion(identifier, props);
});
}
// 开始挑战
const startChallenge = () => {
// 调用 start 接口, 成功后跳转到开启实战
// TODO
identifier && validateOjForm(props, 'challenge', () => {
startProgramQuestion(identifier, props);
});
// identifier && startProgramQuestion(identifier, props);
}
// 取消
const handleClickCancel = () => {
// 清空当前输入值并跳转至列表页
props.clearOJFormStore();
// 清空描述信息
toStore('oj_description', '');
props.history.push('/problems');
}
// 发布
const handleClickPublish = () => {
// ModalConfirm('提示', (<p>发布后即可应用到自己管理的课堂<br /> 是否确认发布?</p>), () => {
// changePublishLoadingStatus(true);
// handlePublish(props, 'publish');
// });
props.confirm({
title: '提示',
content: (<p>发布后即可应用到自己管理的课堂<br /> 是否确认发布?</p>),
onOk () {
changePublishLoadingStatus(true);
handlePublish(props, 'publish');
}
});
}
// 撤销发布
const handleClickCancelPublish = () => {
// ModalConfirm('提示', (<p>是否确认撤销发布?</p>), () => {
// changePublishLoadingStatus(true);
// handleCancelPublish(props, identifier);
// });
props.confirm({
title: '提示',
content: ((<p>是否确认撤销发布?</p>)),
onOk () {
changePublishLoadingStatus(true);
handleCancelPublish(props, identifier);
}
});
}
// 取消保存/取消按钮
const renderSaveOrCancel = () => {
return (
<React.Fragment>
<Button
onClick={handleClickCancel}
style={{ background: '#666666', color: '#fff', border: 'none' }}
>取消</Button>
<Button
type="primary"
loading={submitLoading}
onClick={handleSubmitForm}
>保存</Button>
</React.Fragment>
);
}
// 发布/模拟挑战
const renderPubOrFight = () => {
const pubButton = isPublish
? (<Button
style={{ background: 'rgba(102,102,102,1)', border: 'none' }}
type="primary"
loading={publishLoading}
onClick={handleClickCancelPublish}
>撤销发布</Button>)
: (<Button
type="primary"
loading={publishLoading}
onClick={handleClickPublish}
>立即发布</Button>);
// 未发布: 模拟挑战 已发布: 开始挑战
const challengeBtn = isPublish ? (
<Button type="primary" onClick={startChallenge}>开始挑战</Button>
) : (
<Button type="primary" onClick={imitationChallenge}>模拟挑战</Button>
);
if (isPublish) {
return (
<React.Fragment>
{pubButton}
<Button
type="primary"
loading={submitLoading}
onClick={handleSubmitForm}
>保存</Button>
{challengeBtn}
</React.Fragment>
);
} else {
return (
<React.Fragment>
<Button
type="primary"
loading={submitLoading}
onClick={handleSubmitForm}
>保存</Button>
{pubButton}
{challengeBtn}
</React.Fragment>
);
}
}
// 渲染退出
const renderQuit = () => {
return identifier ? (
<Button type="link"
icon='poweroff'
className='quite_btn'
onClick={handleClickCancel}
>退出</Button>
) : ''
}
return (
<div className={'new_add_task_wrap'}>
<div className={'task_header'}>
<UserInfo userInfo={userInfo}/>
<p className={'header_title'}>{props.name || ''}</p>
{ renderQuit() }
</div>
<div className="split-pane-area">
<SplitPane className='outer-split-pane' split="vertical" minSize={350} maxSize={-350} defaultSize="40%">
<div className={'split-pane-left'}>
<LeftPane />
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane onSubmitForm={handleSubmitForm}/>
<div />
</SplitPane>
</SplitPane>
</div>
{/* 控制台 */}
<div className='new_add_task_ctl'>
{
/* 录入时: 取消 保存 */
/* 保存未发布: 立即发布 模拟挑战 */
}
{ !identifier ? renderSaveOrCancel() : renderPubOrFight() }
</div>
</div>
)
}
const mapStateToProps = (state) => {
const { ojForm, identifier, testCases, isPublish } = state.ojFormReducer;
const { publishLoading, submitLoading } = state.commonReducer;
const { userInfo } = state.userReducer;
return {
name: ojForm.name,
identifier,
testCases,
isPublish, // 是否已发布
publishLoading,
submitLoading,
userInfo
}
};
const mapDispatchToProps = (dispatch) => ({
// 保存提交的代码值
saveOjFormCode: (value) => dispatch(actions.saveOjFormCode(value)),
// 表单提交时,调用表单验证功能
handleFormSubmit: (props) => dispatch(actions.validateOjForm(props)),
// 发布表单
handlePublish: (props, type) => dispatch(actions.validateOjForm(props, type)),
// 撤销发布
handleCancelPublish: (props, identifier) => dispatch(actions.handleClickCancelPublish(props, identifier)),
// 更新OJForm
handleUpdateOjForm: (props) => dispatch(actions.validateOjForm(props)),
// 根据id号获取表单信息
getOJFormById: (id) => dispatch(actions.getOJFormById(id)),
// 保存 OJ form id值
saveOJFormId: (id) => dispatch(actions.saveOJFormId(id)),
// 清空测试用例的集合
clearOJFormStore: () => dispatch(actions.clearOJFormStore()),
// 按钮状态
changeSubmitLoadingStatus: (flag) => dispatch(actions.changeSubmitLoadingStatus(flag)),
// 发布按钮状态
changePublishLoadingStatus: (flag) => dispatch(actions.changePublishLoadingStatus(flag)),
// 测试用例及验证
updateTestAndValidate: (obj) => dispatch(actions.updateTestAndValidate(obj)),
// 开启模拟挑战
startProgramQuestion: (id, props) => dispatch(actions.startProgramQuestion(id, props)),
// 新建时获取信息
getUserInfoForNew: () => dispatch(actions.getUserInfoForNew()),
validateOjForm: (props, type, cb) => dispatch(actions.validateOjForm(props, type, cb)),
getQuestion: (params) => dispatch(actions.getQuestion(params))
});
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(CNotificationHOC() (NewOrEditTask)));

View File

@@ -0,0 +1,32 @@
@import '../split_pane_resizer.scss';
.new_add_task_wrap {
.split-pane-area{
height: calc(100vh - 121px);
}
}
.new_add_task_ctl{
display: flex;
align-items: center;
justify-content: center;
height: 56px;
// background: #333333;
background: rgba(18,28,36,1);
> button{
margin-right: 20px;
}
}
.quite_btn{
position: absolute;
right: 10px;
top: 15px;
margin-left: 30px;
color: #888888;
transition: all .3s;
cursor: pointer;
&:hover{
color: #5091FF;
}
}

View File

@@ -0,0 +1,14 @@
import React, { PureComponent } from 'react';
import connect from 'react-redux';
class CommitTab extends PureComponent {
render () {
return (
<h2>提交页</h2>
)
}
}
// export default connect()(CommitTab);
export default CommitTab;

View File

@@ -0,0 +1,203 @@
/*
* @Description: 添加测试用例
* @Author: tangjiang
* @Github:
* @Date: 2019-11-21 09:19:38
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-27 10:37:41
*/
import './index.scss';
import React from 'react';
import { Collapse, Icon, Input, Form } from 'antd';
import { connect } from 'react-redux';
import actions from '../../../../../redux/actions';
import { CNotificationHOC} from 'educoder';
const { Panel } = Collapse;
const { TextArea } = Input;
const FormItem = Form.Item;
const AddTestDemo = (props) => {
const {
// key,
// onSubmitTest,
onDeleteTest,
testCase,
testCaseValidate,
isOpen
} = props;
// const [isEditor, setIsEditor] = useState(false); // 是否是编辑
// 删除操作
const handleDeletePanel = (e) => {
// console.log('点击的删除按钮')
e.preventDefault();
e.stopPropagation();
props.confirm({
title: '提示',
content: '确定要删除当前测试用例吗?',
onOk() {
onDeleteTest(testCase);
}
});
// Modal.confirm({
// title: '删除',
// content: '确定要删除当前测试用例吗?',
// okText: '确定',
// cancelText: '取消',
// onOk() {
// onDeleteTest(testCase);
// }
// })
}
// 输入框值改变时
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 = () => (
<Icon
type="close"
className="collapse_close_icon"
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
// }
// }
// 渲染提交按钮
// const renderSubmitBtn = () => {
// const { identifier, testCase, loading } = props;
// // console.log('========', identifier);
// // 1. 新增时,不显示按钮
// if (identifier) {
// if (testCase.isAdd) {
// return (
// <FormItem style={{ textAlign: 'right' }}>
// <Button style={{ marginRight: '20px' }} onClick={handleReset}>取消</Button>
// <Button type="primary" onClick={handleSubmit}>保存</Button>
// </FormItem>
// );
// } else {
// return (
// <FormItem style={{ textAlign: 'right' }}>
// <Button onClick={handleEditorOrSave} loading={loading}>{isEditor ? '保存' : (loading ? '保存' : '编辑')}</Button>
// </FormItem>
// );
// }
// }
// }
/**
* 文本输入框可编辑的情况
* 1. 新增时
* 2. isAdd 为 false 且 isEditor 为true 时
* @param {*} testCase
*/
// const isDisabled = (testCase) => {
// return !testCase.isAdd && !isEditor;
// };
// const {input = {}, output = {}} = (testCasesValidate[index] = {});
// const activePane = {
// defaultActiveKey: [isOpen ? '1' : '']
// };
// console.log(activePane);
// 切换手风琴
const handleChangeCollapse = () => {
const {index, updateOpenTestCaseIndex} = props;
updateOpenTestCaseIndex(index);
}
return (
<Collapse className={'collapse_area'} activeKey={isOpen?'1':''} onChange={() => handleChangeCollapse()}>
<Panel header={`测试用例${props.index + 1}`} extra={genExtra()} key="1">
<Form>
<FormItem
label={<span className={'label_text'}>输入</span>}
validateStatus={testCaseValidate.input.validateStatus}
help={testCaseValidate.input.errMsg}
colon={ false }
>
<TextArea
rows={5}
value={testCase.input}
onChange={handleInputChange}
// disabled={isDisabled(testCase)}
/>
</FormItem>
<FormItem
label={<span className={'label_text'}>输出</span>}
validateStatus={testCaseValidate.output.validateStatus}
help={testCaseValidate.output.errMsg}
colon={ false }
>
<TextArea
rows={5}
value={testCase.output}
onChange={handleOutputChange}
// disabled={isDisabled(testCase)}
/>
</FormItem>
{/* {renderSubmitBtn()} */}
</Form>
</Panel>
</Collapse>
);
}
const mapStateToProps = (state) => {
const {identifier, loading} = state.ojFormReducer;
// console.log(state.ojFormReducer);
return {
identifier,
loading,
}
};
const mapDispatchToProps = (dispatch) => ({
testCaseOutputChange: (value, index) => dispatch(actions.testCaseOutputChange(value, index)),
testCaseInputChange: (value, index) => dispatch(actions.testCaseInputChange(value, index)),
updateOpenTestCaseIndex: (index) => dispatch(actions.updateOpenTestCaseIndex(index)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form.create()(CNotificationHOC()(AddTestDemo)));

View File

@@ -0,0 +1,544 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-20 10:35:40
* @LastEditors : tangjiang
* @LastEditTime : 2020-02-05 13:26:58
*/
import './index.scss';
// import 'katex/dist/katex.css';
import React from 'react';
import { Form, Input, Select, InputNumber, Button, Cascader, notification } from 'antd';
import { connect } from 'react-redux';
import AddTestDemo from './AddTestDemo';
// import QuillEditor from '../../../quillEditor';
import actions from '../../../../../redux/actions';
import CONST from '../../../../../constants';
import { toStore } from 'educoder'; // 保存和读取store值
// import Wrapper from '../../../../../common/reactQuill';
import QuillForEditor from '../../../../../common/quillForEditor';
import KnowLedge from '../../../components/knowledge';
const scrollIntoView = require('scroll-into-view');
const {jcLabel} = CONST;
const FormItem = Form.Item;
const { Option } = Select;
const maps = {
language: [
{ title: (<span style={{ color: 'rgba(0, 0, 0, 0.35)' }}>请选择</span>), key: '' },
{ title: 'C', key: 'C' },
{ title: 'C++', key: 'C++' },
{ title: 'Python', key: 'Python' },
{ title: 'Java', key: 'Java' }
],
difficult: [
{ title: (<span style={{ color: 'rgba(0, 0, 0, 0.35)' }}>请选择</span>), key: '' },
{ title: '简单', key: '1' },
{ title: '中等', key: '2'},
{ title: '困难', key: '3' }
],
category: [
{ title: (<span style={{ color: 'rgba(0, 0, 0, 0.35)' }}>请选择</span>), key: '' },
{ title: '程序设计', key: '1' },
{ title: '算法', key: '2'}
],
openOrNot: [
{ title: '公开', key: '1' },
{ title: '私有', key: '0' }
]
}
class EditTab extends React.Component {
constructor (props) {
super(props);
// this.editorRef = React.createRef();
this.scrollRef = React.createRef();
this.headerRef = React.createRef();
this.state = {
scrollEl: null,
targetEl: null,
scrollHeight: 0, // 滚动元素的高度
top: 500,
bottom: 20,
offsetTop: 0,
showAdd: false
// knowledges: [],
// coursers: [] // 选中的课程
}
}
componentDidMount () {
const oWrap = document.getElementById('textCase');
const scrollHeight = oWrap.offsetHeight;
const oTarget = this.headerRef.current;
const offsetTop = oTarget.offsetTop;
this.setState({
scrollEl: oWrap,
targetEl: oTarget,
offsetTop: offsetTop, // 记录初始位置
scrollHeight,
}, () => {
this.state.scrollEl.addEventListener('scroll', this.handleScroll, false);
});
// 获取题库
// this.props.getQuestion({
// source: 'question'
// });
}
// componentDidUpdate (nextProp) {
// console.log(nextProp);
// }
componentWillUnmount (nextPro) {
this.state.scrollEl.removeEventListener('scroll', this.handleScroll, false);
}
// 处理滚动
handleScroll = (e) => {
const oTarget = this.state.targetEl;
const tOffsetTop = oTarget.offsetTop;
const tOffsetHeight = oTarget.offsetHeight;
const scrollTop = e.target.scrollTop;
const { scrollHeight, offsetTop} = this.state;
// 滚动距离 + 元素的高度 大于 offsetTop值时 添加 fix_top 样式
// console.log(tOffsetTop, tOffsetHeight, scrollTop, scrollHeight, offsetTop);
if ((scrollTop + tOffsetHeight > tOffsetTop) && (tOffsetTop >= offsetTop)) {
oTarget.className = `test_demo_title fix_top`;
} else if ((scrollHeight + scrollTop < offsetTop) && (scrollHeight <= offsetTop)){
oTarget.className = `test_demo_title`;
} else if ((tOffsetTop < offsetTop) && (tOffsetTop + tOffsetHeight + scrollTop) < offsetTop) {
oTarget.className = `test_demo_title`;
}
}
// 改变任务名称
handleNameChange = (e) => {
const value = e.target.value;
this.props.validateOJName(value);
}
// 改变语言
handleLanguageChange = (value) => {
this.props.validateOjLanguage(value);
}
// 改变描述信息
handleChangeDescription = (value) => {
// console.log('获取的编辑器内容为: ', value);
// 描述信息变化时将信息保存至store中
toStore('oj_description', value);
this.props.validateOjDescription(value);
}
// 改变难易度
handleChangeDifficult = (value) => {
this.props.validateOjDifficult(value);
}
// 改变时间限制
handleTimeLimitChange = (value) => {
this.props.validateOjTimeLimit(value);
}
// 改变方向
handleChangeSubDisciplineId = (value) => {
// 课程下拉值变化时, 同步更新知识点
const { courseQuestions, saveKnowledge } = this.props;
saveKnowledge([]);
// 获取当前分类下的知识点
courseQuestions.forEach(item => {
if (value[0] && item.id === value[0]) {
item.sub_disciplines && item.sub_disciplines.forEach(c => {
if (value[1] && c.id === value[1]) {
saveKnowledge(c.tag_disciplines)
} else if (!value[1]) {
saveKnowledge([]);
}
});
}
});
this.setState({
showAdd: value[1] ? true : false
});
// this.props.validateOjCategory(value[1] || '');
this.props.validateOjSubDisciplineId(value[1] || '');
}
// 改变公开程序
handleChangeOpenOrNot = (value) => {
this.props.validateOpenOrNot(value);
}
// 滚动到底部
scrollToBottom = () => {
scrollIntoView(this.scrollRef.current);
}
render () {
const { showAdd } = this.state;
const {
ojForm,
ojFormValidate,
// testCases = [], // 测试用例集合
addTestCase, // 添加测试用例
deleteTestCase, // 删除测试用例
testCasesValidate,
openTestCodeIndex = [],
courseQuestions,
tag_discipline_id,
knowledges,
tagDisciplines,
} = this.props;
// console.log('knowledge======>>>>>>', knowledges);
// const {knowledges} = this.state;
// 表单label
const myLabel = (name, subTitle, nostar) => {
if (subTitle) {
return (
<span className={`label_text ${nostar}`}>
{name}
<span className={'label_sub_text'}>
({subTitle})
</span>
</span>
)
} else {
return (
<span className={`label_text ${nostar}`}>{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 this.props.testCases.map((item, i) => {
return <AddTestDemo
key={`${i}`}
isOpen={openTestCodeIndex.includes(i)}
onSubmitTest={handleSubmitTest}
onDeleteTest={handleDeleteTest}
testCase={item}
testCaseValidate={testCasesValidate[i]}
index={i}
/>
});
};
// 添加测试用例
const handleAddTest = () => {
const {position, testCases = []} = this.props;
if (testCases.length >= 50) {
notification.warning({
message: '提示',
description: '测试用例不能超过50个'
});
return;
}
const obj = { // 测试用例参数
input: '',
output: '',
position: position,
isAdd: true // 新增的测试用例
}
const validateObj = { // 测试用例验证参数
input: {
validateStatus: '',
errMsg: ''
},
output: {
validateStatus: '',
errMsg: ''
}
}
// this.scrollRef.current.scrollTo(1000);
addTestCase({testCase: obj, tcValidate: validateObj});
// TODO 点击新增时,需要滚到到最底部
this.scrollToBottom();
}
// 描述信息变化时
const handleContentChange = (content, quill) => {
// console.log('描述信息为: ', content);
const _text = quill.getText();
const reg = /^[\s\S]*.*[^\s][\s\S]*$/;
if (!reg.test(_text)) {
this.handleChangeDescription('');
} else {
// 保存获取的描述信息至redux中
this.handleChangeDescription(content);
}
}
// 编辑器配置信息
const quillConfig = [
{ header: 1}, {header: 2},
// {size: ['12px', '14px', '16px', '18px', '20px', false]},
'bold', 'italic', 'underline', 'strike', // 切换按钮
'blockquote', 'code-block', // 代码块
{align: []}, { 'list': 'ordered' }, { 'list': 'bullet' }, // 列表
{ 'script': 'sub'}, { 'script': 'super' },
{ 'color': [] }, { 'background': [] }, // 字体颜色与背景色
// {font: []},
'image', 'formula', // 数学公式、图片、视频
'clean', // 清除格式
// 'fill',
];
const renderCourseQuestion = (arrs) => {
const tempArr = [];
const sub_id = this.props.ojForm.sub_discipline_id;
function loop (arrs, tempArr) {
arrs.forEach(item => {
const obj = {};
obj.value = item.id;
obj.label = item.name;
// 当item下还有子元素时递归调用
if (item.sub_disciplines) {
arrs = item.sub_disciplines;
obj.children = [];
loop(arrs, obj.children);
}
tempArr.push(obj);
});
}
loop(arrs, tempArr);
// 获取选中的下拉值
let choid_ids = [];
// let tempKnowledges = [];
tempArr.forEach(t => {
// debugger;
if (sub_id && t.children) {
t.children.forEach(c => {
if (c.value === +sub_id) {
choid_ids = [t.value, c.value];
// tempKnowledges = c.children || [];
}
});
}
});
return (
<Cascader
placeholder="请选择"
options={tempArr}
expandTrigger="hover"
value={choid_ids}
// onChange={this.handleChangeCategory}
onChange={this.handleChangeSubDisciplineId}
/>
)
}
// 知识点
const handleKnowledgeChange = (values= []) => {
const _result = [];
values.forEach(v => {
_result.push(v.id);
});
// console.log('下拉选择的值:===>>>', _result);
// 保存选择的知识点
this.props.saveTagDisciplineId(_result);
}
// 新增知识点
const handleAddKnowledge = (values) => {
// console.log('调用了新增知识点并返回了结果: ', values);
// 获取课程id
const {sub_discipline_id} = this.props.ojForm;
const obj = Object.assign({}, values, {sub_discipline_id})
tagDisciplines(obj);
}
return (
<div className={'editor_area'} id="textCase">
<Form className={'editor_form'}>
<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} value={`${ojForm.difficult}`}>
{getOptions('difficult')}
</Select>
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_right`}
label={<span>{myLabel(jcLabel['sub_discipline_id'], '合理的课程分类有利于快速检索')}</span>}
validateStatus={ojFormValidate.sub_discipline_id.validateStatus}
help={ojFormValidate.sub_discipline_id.errMsg}
colon={ false }
>
{/* <Select onChange={this.handleChangeCategory} value={`${ojForm.category}`}>
{getOptions('category')}
</Select> */}
{/* <Cascader
options={courseQuestions}
expandTrigger="hover"
onChange={this.handleChangeCategory}
/> */}
{ renderCourseQuestion(courseQuestions)}
</FormItem>
<FormItem
colon={ false }
className='input_area flex_100'
label={<span>{myLabel(jcLabel['knowledge'], '', 'nostar')}</span>}
>
<KnowLedge
showAdd={showAdd}
options={knowledges}
values={tag_discipline_id}
onChange={handleKnowledgeChange}
addKnowledge={handleAddKnowledge}
/>
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_left`}
label={<span>{myLabel(jcLabel['timeLimit'], '程序允许时间限制时长,单位:秒')}</span>}
validateStatus={ojFormValidate.timeLimit.validateStatus}
help={ojFormValidate.timeLimit.errMsg}
colon={ false }
>
<InputNumber value={ojForm.timeLimit} min={0} max={5} style={{ width: '100%' }} onChange={this.handleTimeLimitChange}/>
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_right`}
label={<span>{myLabel(jcLabel['language'])}</span>}
validateStatus={ojFormValidate.language.validateStatus}
help={ojFormValidate.language.errMsg}
colon={ false }
>
<Select onChange={this.handleLanguageChange} value={`${ojForm.language}`}>
{getOptions('language')}
</Select>
</FormItem>
<FormItem
className={`input_area flex_100`}
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_100`}
label={<span>{myLabel(jcLabel['description'])}</span>}
validateStatus={ojFormValidate.description.validateStatus}
help={ojFormValidate.description.errMsg}
colon={ false }
>
<QuillForEditor
autoFocus={true}
style={{ height: '200px' }}
placeholder="请输入描述信息"
onContentChange={handleContentChange}
options={quillConfig}
value={ojForm.description}
/>
</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} value={`${ojForm.openOrNot}`}>
{getOptions('openOrNot')}
</Select>
</FormItem> */}
</Form>
{/* 添加测试用例 */}
<div className={'test_demo_title'} ref={this.headerRef}>
<h2>测试用例</h2>
<Button type="primary" ghost onClick={handleAddTest}>添加测试用例</Button>
</div>
<div className="test_demo_ctx">
{ renderTestCase() }
</div>
<div style={ {float:"left", clear: "both", background: 'red' } } ref={this.scrollRef}></div>
</div>
)
}
}
const mapStateToProps = (state) => {
const ojFormReducer = state.ojFormReducer;
const {
ojForm,
position,
testCases,
openTestCodeIndex,
testCasesValidate,
ojFormValidate,
courseQuestions,
tag_discipline_id,
knowledges
} = ojFormReducer;
return {
ojForm,
testCases,
testCasesValidate,
ojFormValidate,
position,
openTestCodeIndex,
courseQuestions,
tag_discipline_id,
knowledges
};
};
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)),
validateOjSubDisciplineId: (value) => dispatch(actions.validateOjSubDisciplineId(value)),
saveTagDisciplineId: (value) => dispatch(actions.saveTagDisciplineId(value)),
// 新增测试用例
addTestCase: (value) => dispatch(actions.addTestCase(value)),
// 删除测试用例
deleteTestCase: (value) => dispatch(actions.deleteTestCase(value)),
saveKnowledge: (value) => dispatch(actions.saveKnowledge(value)),
tagDisciplines: (params) => dispatch(actions.tagDisciplines(params))
// 获取题库
// getQuestion: (params) => dispatch(actions.getQuestion(params))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(EditTab);

View File

@@ -0,0 +1,109 @@
.editor_area{
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: '*';
}
&.nostar{
&::before {
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 20px;
.ant-form-explain{
margin-top: 5px;
margin-left: -10px;
}
}
.test_demo_title{
display: flex;
justify-content: space-between;
align-items: center;
height: 60px;
border-bottom: 1px solid #d9d9d9;
margin-bottom: 20px;
&.fix_top{
position: absolute;
top: 43px;
left: -20px;
right: -20px;
padding: 0 26px 0 20px;
// background: gold;
background: rgb(249,249,249);
z-index: 1000;
}
}
.collapse_area{
margin-bottom: 20px;
.ant-form-item{
margin-bottom: 0px;
}
}
}
.collapse_close_icon{
position: relative;
background: rgba(235, 235, 235, 1);
border-radius: 50%;
font-size: 12px;
padding: 5px 5px;
color: rgb(142, 142, 142);
transition: all .3s;
&:hover{
color: #fff;
background: rgb(231, 81, 79);
}
// &:hover{
// color: red;
// }
}

View File

@@ -0,0 +1,334 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-01 09:17:07
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-02 16:33:35
*/
import 'quill/dist/quill.core.css';
import 'quill/dist/quill.bubble.css';
import 'quill/dist/quill.snow.css';
import './index.scss';
import React, { useState, useImperativeHandle, useRef, useEffect } from 'react';
import { Form, Input, InputNumber, Button, Select } from 'antd';
import { connect } from 'react-redux';
import AddTestDemo from './AddTestDemo';
import QuillEditor from '../../../quillEditor';
import actions from '../../../../../redux/actions';
import CONST from '../../../../../constants';
const {jcLabel} = CONST;
const { Option } = Select;
const FormItem = Form.Item;
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' }
]
}
function EditTab (props, ref) {
const {
form,
ojForm,
position,
testCases,
addTestCase,
deleteTestCase,
testCasesValidate,
getFormData
} = props;
const { getFieldDecorator } = form;
const formRef = useRef(null);
const [description, setDescription] = useState('');
// 获取表单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>
);
});
};
// 向外暴露的方法
useImperativeHandle(ref, () => ({
validateForm () {
props.form.validateFields((err, values) => {
if (!err) {
getFormData(() => {
return values;
});
} else {
return;
}
})
}
}));
// 添加测试用例
const handleAddTest = () => {
const obj = { // 测试用例参数
input: '',
output: '',
position: position,
isAdd: true // 新增的测试用例
}
const validateObj = { // 测试用例验证参数
input: {
validateStatus: '',
errMsg: ''
},
output: {
validateStatus: '',
errMsg: ''
}
}
addTestCase({testCase: obj, tcValidate: validateObj});
// TODO 点击新增时,需要滚到到最底部
// this.editorRef.current.scrollTop
// const oDiv = this.editorRef.current;
// oDiv.scrollTo(oDiv.scrollLeft, 99999);
// console.log(oDiv.scrollTop);
// oDiv.scrollTop = 99999;
}
// 渲染测试用例
const renderTestCase = () => {
return testCases.map((item, i) => {
return (
<AddTestDemo
key={`key_${i}`}
onSubmitTest={handleSubmitTest}
onDeleteTest={handleDeleteTest}
testCase={item}
testCaseValidate={testCasesValidate[i]}
index={i}
/>
)
});
};
// 提交测试用例
const handleSubmitTest = (obj) => {
console.log('提交的测试用例: ', obj);
};
// 删除测试用例
const handleDeleteTest = (obj) => {
console.log('删除的测试用例: ', obj);
deleteTestCase(obj);
};
// 描述信息改变时
const handleChangeDescription = (value) => {
console.log('描述信息改变: ', value);
if (value) {
setDescription(value);
}
}
useEffect(() => {
if (description) {
props.form.setFieldsValue({
description: description
}, function () {
console.log('设置成功。。。');
});
}
}, [description]);
return (
<div className={'editor_area'}>
<Form
hideRequiredMark={true}
className={'editor_form'}
ref={formRef}>
<FormItem
className={`input_area flex_60`}
label={<span>{myLabel(jcLabel['name'])}</span>}
>
{
getFieldDecorator('name', {
rules: [
{ required: true, message: '任务名称不能为空' }
],
initialValue: ojForm.name
})(<Input placeholder="请输入任务名称"/>)
}
</FormItem>
<FormItem
className={`input_area flex_40`}
label={<span>{myLabel(jcLabel['language'])}</span>}
>
{
getFieldDecorator('language', {
rules: [
{ required: true, message: '语言不能为空' }
],
initialValue: ojForm.language
})(
<Select>
{getOptions('language')}
</Select>)
}
</FormItem>
<FormItem
className={`input_area flex_100`}
label={<span>{myLabel(jcLabel['description'])}</span>}
>
{
getFieldDecorator('description', {
rules: [
{ required: true, message: '描述信息不能为空' }
],
initialValue: ojForm.description
})(<QuillEditor
style={{ height: '300px' }}
placeholder="请输入描述信息"
htmlCtx={ojForm.description}
onEditorChange={handleChangeDescription}
/>)
}
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_left`}
label={<span>{myLabel(jcLabel['difficult'], '任务的难易程度')}</span>}
>
{
getFieldDecorator('difficult', {
rules: [
{ required: true, message: '难度不能为空' }
],
initialValue: `${ojForm.difficult || ''}`
})(
<Select>
{getOptions('difficult')}
</Select>
)
}
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_right`}
label={<span>{myLabel(jcLabel['timeLimit'], '程序允许时间限制时长,单位:秒')}</span>}
>
{
getFieldDecorator('timeLimit', {
rules: [
{ required: true, message: '时间限制不能为空' }
],
initialValue: ojForm.timeLimit
})(<InputNumber min={0} style={{ width: '100%' }} />)
}
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_left`}
label={<span>{myLabel(jcLabel['category'], '任务所属分类')}</span>}
>
{
getFieldDecorator('category', {
rules: [
{ required: true, message: '任务名称不能为空' }
],
initialValue: `${ojForm.category || ''}`
})(
<Select>
{getOptions('category')}
</Select>
)
}
</FormItem>
<FormItem
className={`input_area flex_50 flex_50_right`}
label={<span>{myLabel(jcLabel['openOrNot'])}</span>}
>
{
getFieldDecorator('openOrNot', {
rules: [
{ required: true, message: '任务名称不能为空' }
],
initialValue: `${ojForm.openOrNot}`
})(
<Select>
{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 mapStateToProps = (state) => {
const ojFormReducer = state.ojFormReducer;
const {ojForm, position, testCases, testCasesValidate} = ojFormReducer;
return {
ojForm,
testCases,
testCasesValidate,
position
};
}
const mapDispatchToProps = (dispatch) => ({
// 新增测试用例
addTestCase: (value) => dispatch(actions.addTestCase(value)),
// 删除测试用例
deleteTestCase: (value) => dispatch(actions.deleteTestCase(value)),
})
// EditTab = React.formRef(EditTab);
// EditTab = React.forwardRef(EditTab);
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form.create()(
React.forwardRef(EditTab)
));

View File

@@ -0,0 +1,68 @@
/*
* @Description: 左侧编辑 / 评论 / 提交记录
* @Author: tangjiang
* @Date: 2019-11-19 11:35:30
* @Last Modified by: tangjiang
* @Last Modified time: 2019-11-19 19:07:02
*/
import './index.scss';
import React, { useState, useMemo } from 'react';
// import { Tabs } from 'antd';
import EditorTab from './editorTab';
import PrevTab from './prevTab';
// const { TabPane } = Tabs;
function LeftPane (props) {
const navItem = [
{
title: '编辑',
key: 'editor'
}, {
title: '预览',
key: 'prev'
}
];
const Comp = {
editor: (<EditorTab />),
prev: (<PrevTab />)
};
const [defaultActiveKey, setDefaultActiveKey] = useState('editor');
const renderComp = useMemo(() => {
return Comp[defaultActiveKey];
}, [defaultActiveKey]);
const renderNavItem = navItem.map((item) => {
const _classes = item.key === defaultActiveKey ? 'add_editor_item active' : 'add_editor_item';
return (
<li
key={item.key}
className={_classes}
onClick={() => setDefaultActiveKey(item.key)}
>
<span className={'item-span'}>{item.title}</span>
</li>
)
});
return (
// <Tabs activeKey={defaultActiveKey} onChange={handleTabChange}>
// { tabs }
// </Tabs>
<React.Fragment>
<ul className={'add_editor_list_area'}>
{ renderNavItem }
</ul>
<div className="comp_ctx" style={{ height: 'calc(100vh - 177px)' }}>
{ renderComp }
</div>
</React.Fragment>
)
};
export default LeftPane;

View File

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

View File

@@ -0,0 +1,61 @@
/*
* @Description: 代码预览页面
* @Author: tangjiang
* @Github:
* @Date: 2019-11-24 10:09:55
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-18 10:02:24
*/
import './index.scss';
import React, { useEffect, useState, useRef } from 'react';
import { connect } from 'react-redux';
import {Empty} from 'antd';
// import Wrapper from '../../../../../common/reactQuill';
import QuillForEditor from '../../../../../common/quillForEditor';
const PrevTab = (props) => {
const prevRef = useRef(null);
// const [desc, setDesc] = useState('');
const [renderCtx, setRenderCtx] = useState(() => '');
// 渲染内容
useEffect(() => {
if (props.description) {
setRenderCtx(() => (
<div
id="quill_editor"
style = {{ height: '100%', width: '100%'}}
ref={prevRef}>
<QuillForEditor
readOnly={true}
value={props.description}
/>
</div>
));
} else {
setRenderCtx(() => (
<div className='no_result'>
<Empty />
</div>
));
}
}, [props]);
return (
<div className={`prev_area`}>
{renderCtx}
</div>
)
}
const mapStateToProps = (state) => {
const { ojForm } = state.ojFormReducer;
return {
description: ojForm.description
}
}
export default connect(
mapStateToProps
)(PrevTab);

View File

@@ -0,0 +1,17 @@
.no_result{
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.render_html{
padding: 20px 30px;
}
.prev_area{
// padding: 0 30px;
padding-left: 20px;
height: calc(100vh - 110px);
}

View File

@@ -0,0 +1,80 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-12-01 10:18:35
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-27 19:33:50
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import MyMonacoEditor from '../../components/myMonacoEditor';
// import ControlSetting from '../../components/controlSetting';
import actions from '../../../../redux/actions';
function RightPane (props, ref) {
const {
// identifier,
code,
showCode,
language,
// onSubmitForm,
saveOjFormCode
} = props;
// let timer = null;
// 代码改变时,保存
const handleCodeChange = (updateCode) => {
// if (props.identifier) {
// // 保存用户输入的代码
// if (!timer) {
// timer = setInterval(() => {
// clearInterval(timer);
// timer = null;
// }, 3000);
// }
// }
saveOjFormCode(updateCode);
}
// 启动调试代码
// const handleDebuggerCode = (value) => {
// console.log('调用的代码调试====', value);
// }
return (
<div className={'right_pane_code_wrap'}>
<MyMonacoEditor
language={language}
code={showCode}
onCodeChange={handleCodeChange}/>
{/* <ControlSetting
// identifier={identifier}
inputValue={props.input}
onSubmitForm={onSubmitForm}
// onDebuggerCode={handleDebuggerCode}
/> */}
</div>
)
}
const mapStateToProps = (state) => {
const { ojForm, testCases, code, identifier, showCode } = state.ojFormReducer;
return {
code,
showCode,
identifier,
language: ojForm.language,
input: (testCases[0] && testCases[0].input) || '',
}
};
const mapDispatchToProps = (dispatch) => ({
// 保存提交的代码值
saveOjFormCode: (value) => dispatch(actions.saveOjFormCode(value)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(RightPane);

View File

@@ -0,0 +1,140 @@
.right_pane_code_wrap{
position: relative;
// justify-content: center;
// background-color: #222;
height: 100%;
// height: calc(100vh - 178px);
.code-title,
.controller-pane,
.pane_control_opts{
display: flex;
align-items: center;
justify-content: space-between;
// padding: 0 30px;
// background: #000;
background: rgba(18,28,36,1);
color: #fff;
}
.code-title,
.pane_control_opts{
padding: 0 20px;
}
.code-title{
height: 56px;
.code-icon{
cursor: pointer;
}
}
// .controller-pane{
// min-height: 56px;
// background-color: #222;
// }
.code-pane-wrap{
height: 800px;
// position: absolute;
// top: 56px;
// bottom: 56px;
// width: 100%;
}
.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;
}
}
}
.pane_control_opts{
height: 56px;
}
.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;
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;
}
}

View File

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

View File

@@ -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 10px;
color: #fff;
}
.flex_r{
padding: 0 20px 0 10px;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,134 @@
/*
* @Description: Quill 编辑器
* @Author: tangjiang
* @Github:
* @Date: 2019-11-25 09:46:03
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 16:10:23
*/
// 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 'katex/dist/katex.min.css';
import React from 'react';
import katex from 'katex';
const Quill = require('quill');
// 将katex挂载到window上
window.katex = katex;
window.Quill = Quill;
// const Quill = window.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;
// console.log(placeholder);
const renderOptions = options || defaultOptions;
const editorOption = {
placeholder: placeholder,
modules: {
toolbar: renderOptions
},
readOnly,
theme: readOnly ? 'bubble' : '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;
}
}, 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;

View File

@@ -0,0 +1,164 @@
/*
* @Description: 提交记录详情
* @Author: tangjiang
* @Github:
* @Date: 2019-12-04 08:36:21
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-02 13:48:02
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
import { Button } from 'antd';
import moment from 'moment';
import ErrorResult from '../components/errorResult';
import { Link } from 'react-router-dom';
import MonacoEditor from '@monaco-editor/react';
import { connect } from 'react-redux';
// import { getImageUrl } from 'educoder';
import { withRouter } from 'react-router'
import actions from '../../../redux/actions';
import CONST from '../../../constants';
import UserInfo from '../components/userInfo';
const {reviewResult} = CONST;
function RecordDetail (props) {
const {
match: { params },
recordDetail,
// identifier,
getUserCommitRecordDetail,
saveEditorCodeForDetail
} = props;
const id = params.id;
const [detail, setDetail] = useState({});
const [user, setUser] = useState({});
const [identifier, setIdentifier] = useState('');
useEffect(() => {
// 根据id获取记录详情
getUserCommitRecordDetail(id, 'detail');
}, []);
useEffect(() => {
setDetail(recordDetail);
// console.log('详情: ', recordDetail);
if (recordDetail) {
const { user, myproblem_identifier, code } = recordDetail;
setUser(user);
setIdentifier(myproblem_identifier);
if (code) {
saveEditorCodeForDetail(code);
}
}
}, [recordDetail]);
const handleReturn = (identifier) => {
if (identifier) {
saveEditorCodeForDetail('');
setTimeout(() => {
props.history.push(`/myproblems/${identifier}`);
}, 300);
}
}
const handleEditorCode = (identifier, code) => {
if (identifier) {
console.log(code);
saveEditorCodeForDetail(code);
props.history.push(`/myproblems/${identifier}`);
}
}
return (
<div className="record_detail_area">
<div className="record_detail_header">
{/* <div className="avator_nicker">
<img alt="用户头像" className={'student_img'} src={getImageUrl( (user && `images/${user.image_url}`)|| 'images/educoder/headNavLogo.png?1526520218')} />
<span className={'student_nicker'}>
{(user && user.name) || ''}
</span>
</div> */}
<UserInfo userInfo={user || {}}/>
<div className={'study_name'}>
<span>{detail.name || 'test'}</span>
</div>
<div className={'study_quit'}>
<Button style={{ visibility: identifier ? 'visible' : 'hidden'}} onClick={() => handleReturn(identifier)}>
返回该题
{/* <Link to={`/myproblems/${identifier}`}>返回该题</Link> */}
</Button>
</div>
</div>
<div className="record_detail_ctx">
<div className="detail_ctx_header">
<h2 className="header_h2">提交记录</h2>
</div>
<div className="detail_ctx_status">
<span className="status_label">
状态: <span className={detail.status === 0 ? 'status_label_success' : 'status_label_error'}>{reviewResult[detail.status]}</span>
</span>
<span className="status_label">
提交时间: <span className="status_label_sub">
{moment(detail.created_at).format('YYYY-MM-DD HH:mm')}
</span>
</span>
<span className="status_label">
语言: <span className="status_label_sub">{detail.language}</span>
</span>
<span className="status_label" style={{ visibility: detail.status === 0 ? 'visible' : 'hidden'}}>
执行用时: <span className="status_label_sub">{`${detail.execute_time && Number(detail.execute_time * 1000).toFixed(2)}ms`}</span>
</span>
<span className="status_label pass_case" style={{ display: [-1, 0, 2, 5].includes(detail.status) ? 'inline-block' : 'none'}}>
<span className="status_label_sub">{detail.pass_sets_count}</span>
<span className="pass_case_span"> / {detail.set_count}</span>
个通过测试用例
</span>
</div>
<div className="result_error_area">
<ErrorResult detail={detail}/>
</div>
<div className="detail_ctx_header">
<h2 className="header_h2">提交内容</h2>
<Button
style={{ visibility: identifier ? 'visible' : 'hidden'}}
className={'header_btn'}
type="primary"
onClick={() => handleEditorCode(identifier, detail.code)}
>
编辑代码
{/* <Link to={`/myproblems/${identifier}`}>编辑代码</Link> */}
</Button>
</div>
<div className="result_code_area">
<MonacoEditor
height="100%"
width="100%"
language={(detail.language && detail.language.toLowerCase()) || ''}
value={detail.code || ''}
theme="dark"
readOnly={true}
/>
</div>
</div>
</div>
)
}
const mapStateToProps = (state) => {
const {recordDetail} = state.ojForUserReducer;
return {
// identifier: user_program_identifier,
recordDetail
}
}
const mapDispatchToProps = (dispatch) => ({
// 根据id号获取记录详情
getUserCommitRecordDetail: (id, type) => dispatch(actions.getUserCommitRecordDetail(id, type)),
saveEditorCodeForDetail: (code) => dispatch(actions.saveEditorCodeForDetail(code))
});
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(RecordDetail));

View File

@@ -0,0 +1,55 @@
@import '../split_pane_resizer.scss';
.record_detail_area{
background: #fff;
.record_detail_ctx{
padding: 0 20px;
.detail_ctx_header{
position: relative;
height: 56px;
}
.header_h2{
line-height: 56px;
}
.header_btn{
position: absolute;
right: 0;
top: 14px;
}
.detail_ctx_status{
height: 18px;
line-height: 18px;
.status_label{
color: rgba(153,153,153,1);
margin-right: 40px;
}
.status_label_error{
color: #E51C24;
}
.status_label_success{
color: #28BD8B;
}
.status_label_sub{
color: #333333;
}
.pass_case{
float: right;
margin-right: 0;
}
.pass_case_span{
margin-right: 10px;
}
}
.result_code_area{
// height: 500px;
height: calc(100vh - 360px);
}
.result_error_area{
margin-top: 15px;
background: rgba(250,250,250,1);
color: #E51C24;
border-radius: 3px;
}
}
}

View File

@@ -0,0 +1,258 @@
.new_add_task_wrap,
.student_study_warp,
.record_detail_area{
height: 100vh;
.task_header,
.student_study_header,
.record_detail_header{
height: 65px;
// background:rgba(34,34,34,1);
// background: #1E1E1E;
background: rgba(7,15,25,1);
padding:0 20px;
}
.task_header{
position: relative;
line-height: 65px;
.header_btn,
.header_title{
color: #fff;
line-height: 65px;
}
.header_title{
font-size: 16px;
font-weight: bold;
text-align: center;
}
.header_btn{
position: absolute;
line-height: 32px;
top: 18px;
&:last-child{
right: 30px;
}
}
// .header_btn{
// width: 88px;
// }
// .header_title{
// flex: 1;
// text-align: center;
// }
}
.split-pane-area,
.pane_right_area
{
position: relative;
}
.student_study_header,
.record_detail_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: 10px;
}
.student_img{
width: 30px;
height: 30px;
}
.study_quit{
float: right;
}
.study_name{
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0;
text-align: center;
font-size: 16px;
font-weight: bold;
}
}
}
.add_editor_list_area{
background: #fff;
padding: 0 20px;
margin: 0;
.add_editor_item{
display: inline-block;
height: 56px;
line-height: 56px;
box-sizing: border-box;
margin-right: 30px;
border-bottom: 2px solid transparent;
transition: all .3s;
cursor: pointer;
.item-span{
color: #666;
font-size: 16px;
}
// > span{
// cursor: pointer;
// }
&.active{
border-bottom-color: #5091FF;
.item-span{
color: #5091FF;
}
}
}
}
.comp_ctx{
// height: calc(100vh - 178px);
overflow-y: hidden;
}
.split-pane-area,
.split-pane-left{
.ant-tabs-nav-wrap{
// padding: 0 30px;
padding: 0 20px;
}
.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: 100%;
overflow-y: auto;
}
}
.Resizer {
position: relative;
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;
// &::before{
// position: absolute;
// width: 24px;
// height: 24px;
// border-radius: 50%;
// margin-top: -12px;
// top: 50%;
// right: -12px;
// font-family: 'iconfont';
// background: gold;
// content: '\e711';
// font-size: 18px;
// text-align: center;
// line-height: 24px;
// }
}
.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;
}
.outer-split-pane{
& > .Resizer{
position: relative;
&::before,
&::after{
position: absolute;
right: -12px;
top: 50%;
transition: opacity, background .3s;
}
&::before{
content: '';
border-radius: 50%;
background: rgba(235,235,235,.3);
width: 24px;
height: 24px;
// font-size: 12px;
}
&::after{
content: '\e712';
font-family: 'iconfont';
transform: scale(.7);
color: #666;
margin-top: -2px;
right: -14px;
opacity: .3;
}
&:hover{
&::before{
background: rgba(235,235,235, 1);
}
&::after{
opacity: 1;
}
}
}
}

View File

@@ -0,0 +1,193 @@
/*
* @Description: 学员学习
* @Author: tangjiang
* @Github:
* @Date: 2019-11-23 10:53:19
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-06 15:27:34
*/
import './index.scss';
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import SplitPane from 'react-split-pane';
import LeftPane from './leftpane';
import RightPane from './rightpane';
// import { Link } from 'react-router-dom';
// import { getImageUrl } from 'educoder'
// import RightPane from '../newOrEditTask/rightpane';
import { Icon } from 'antd';
import UserInfo from '../components/userInfo';
import actions from '../../../redux/actions';
import { fromStore, CNotificationHOC} from 'educoder';
import { withRouter } from 'react-router';
function StudentStudy (props) {
const [hasUpdate, setHasUpdate] = useState(true);
const {
hack,
userInfo,
// hack_identifier,
// user_program_identifier,
restoreInitialCode,
changeUserCodeTab,
changeShowOrHideControl
} = props;
const {
match: { params },
getUserProgramDetail,
saveUserProgramIdentifier
} = props;
let { id, tab } = params;
useEffect(() => {
// 保存当前的id
saveUserProgramIdentifier(id);
// startProgramQuestion(id);
getUserProgramDetail(id);
if (tab) {
changeUserCodeTab(tab);
}
}, []);
useEffect(() => {
const { hack = {} } = props;
if (hack.modify_code && hasUpdate) { // 代码更改,提示是否需要更新代码
setHasUpdate(false);
handleUpdateNotice();
}
}, [props, hasUpdate, setHasUpdate]);
const handleUpdateNotice = () => {
console.log(props);
props.confirm({
title: '提示',
content: (
<p>
代码文件有更新啦 <br />
还未提交的代码请自行保存
</p>
),
onOk () {
restoreInitialCode(id, '更新成功');
}
})
// Modal.confirm({
// title: '提示',
// content: (
// <p>
// 代码文件有更新啦 <br />
// 还未提交的代码,请自行保存
// </p>
// ),
// okText: '立即更新',
// cancelText: '稍后再说',
// onOk () {
// restoreInitialCode(id, '更新成功');
// }
// });
}
// const _hack_id = hack_identifier || fromStore('hack_identifier');
// 处理编辑
const handleClickEditor = (identifier) => {
if (!identifier) return;
changeShowOrHideControl(false);
props.saveEditorCodeForDetail('');
props.clearOjForUserReducer();
props.history.push(`/problems/${identifier}/edit`);
}
// 处理退出
const handleClickQuit = () => {
// 退出时,清空内容
props.clearOjForUserReducer();
// 将控制台关闭
changeShowOrHideControl(false);
props.saveEditorCodeForDetail('');
props.history.push('/problems');
}
return (
<div className={'student_study_warp'}>
<div className={'student_study_header'}>
{/* <div className={'avator_nicker'}>
<img alt="用户头像" className={'student_img'} src={getImageUrl((mygetHelmetapi && mygetHelmetapi.nav_logo_url) || 'images/educoder/headNavLogo.png?1526520218')} />
<span className={'student_nicker'}>
{(mygetHelmetapi &&mygetHelmetapi.name) || ''}
</span>
</div> */}
<UserInfo userInfo={userInfo}/>
<div className={'study_name'}>
<span>{hack.name}</span>
</div>
<div className={'study_quit'}>
{/* to={`/problems/${_hack_id}/edit`} */}
<span
style={{ display: userInfo.hack_manager ? 'inline-block' : 'none' }}
onClick={() => handleClickEditor(hack.identifier)}
className={`quit-btn`}
>
<Icon type="form" className="quit-icon"/> 编辑
</span>
{/* to="/problems" */}
<span onClick={handleClickQuit} className="quit-btn">
<Icon type="poweroff" className="quit-icon"/> 退出
</span>
{/* <Button type="link" icon="form" className='quit-btn'>
<Link to="/problems">编辑</Link>
</Button>
<Button type="link" icon="poweroff" className='quit-btn'>
<Link to="/problems">退出</Link>
</Button> */}
</div>
</div>
<div className="split-pane-area">
<SplitPane className="outer-split-pane" split="vertical" minSize={350} maxSize={-350} defaultSize="40%">
<div className={'split-pane-left'}>
<LeftPane />
</div>
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane
updateNotice={handleUpdateNotice}
/>
<div />
</SplitPane>
</SplitPane>
</div>
</div>
)
}
const mapStateToProps = (state) => {
const { userInfo } = state.userReducer;
const { hack_identifier, user_program_identifier, hack } = state.ojForUserReducer;
return {
hack,
userInfo,
user_program_identifier,
hack_identifier
};
};
const mapDispatchToProps = (dispatch) => ({
// 调用开启编辑
// startProgramQuestion: (id) => dispatch(actions.startProgramQuestion(id))
// 调用编程题详情
getUserProgramDetail: (id) => dispatch(actions.getUserProgramDetail(id)),
saveUserProgramIdentifier: (id) => dispatch(actions.saveUserProgramIdentifier(id)),
saveEditorCodeForDetail: (code) => dispatch(actions.saveEditorCodeForDetail(code)),
// 恢复初始代码
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
changeShowOrHideControl: (flag) => dispatch(actions.changeShowOrHideControl(flag)),
clearOjForUserReducer: () => dispatch(actions.clearOjForUserReducer()),
changeUserCodeTab: (tab) => dispatch(actions.changeUserCodeTab(tab))
});
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps
)(CNotificationHOC()(StudentStudy)));

View File

@@ -0,0 +1,56 @@
@import '../split_pane_resizer.scss';
.split-pane-area{
height: calc(100vh - 65px);
.right_pane_code_wrap{
position: relative;
.editor_nodte_area,
.student_notes{
position: absolute;
z-index: 100;
}
.student_notes{
right: 0px;
top: 50%;
width: 36px;
height: 36px;
margin-top: -18px;
border-radius: 50%;
background: #5091FF;
color: #fff;
font-size: 18px;
text-align: center;
transform: translateX(18px);
cursor: pointer;
opacity: 0.5;
transition: all .3s;
&:hover{
opacity: 1;
transform: translateX(-10px);
}
}
.editor_nodte_area{
right: 10px;
top: 50%;
width: 450px;
height: 200px;
margin-top: -100px;
background: #fff;
border-radius: 5px;
padding: 14px 10px 0;
// opacity: ;
transform: translateX(500px);
transition: all .3s;
&.active{
transform: translateX(0px);
}
}
}
}
.right_pane_code_wrap{
position: relative;
// background-color: #222;
height: 100%;
}

View File

@@ -0,0 +1,149 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 09:49:35
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-26 10:43:45
*/
import './index.scss';
import React, { useEffect, useState } from 'react';
import Comment from '../../../../../common/components/comment';
import { connect } from 'react-redux';
import actions from '../../../../../redux/actions';
import { Pagination } from 'antd';
const CommentTask = (props) => {
// 当前页
const [current, setCurrent] = useState(1);
const {
pages,
isAdmin,
identifier,
commentLists,
addComment,
likeComment,
deleteComment,
getCommentLists,
showOrHideComment,
replayChildComment,
changePagination
} = props;
useEffect(() => {
if (identifier) {
// 获取评论列表数据
getCommentLists(identifier);
}
}, [identifier]);
// 添加评论
const handleAddComment = (ctx) => {
addComment(identifier, {
comments: {
content: ctx
}
});
};
// 添加子评论
const handleAddChildComment = (parentId, ctx) => {
replayChildComment(identifier, {
comments: {
content: ctx,
parent_id: parentId
}
});
}
// 删除评论
const handleSubmitDeleteComment = (id) => {
// console.log('删除评论:', identifier, id);
deleteComment(identifier, id);
}
// 点赞
const handleLikeComment = (id) => {
likeComment(identifier, id, {
container_type: 'Discuss',
type: 1
});
}
// 显示或隐藏
const handleShowOrHideComment = (id, hidden) => {
showOrHideComment(identifier, id, {
hidden
});
}
// 点击分页
const handlePageChange = (page, pageSize) => {
setCurrent(page);
changePagination(page);
// setTimeout(() => {
// 调用查询接口
getCommentLists(identifier);
// }, 300);
}
const _style = {
display: pages.total > pages.limit ? 'block' : 'none'
}
return (
<div className="task_comment_task">
<Comment
isAdmin={isAdmin}
commentLists={commentLists}
addComment={handleAddComment}
addChildComment={handleAddChildComment}
likeComment={handleLikeComment}
showOrHideComment={handleShowOrHideComment}
submitDeleteComment={handleSubmitDeleteComment}
/>
<div className="task_comment_page" style={ _style }>
<Pagination
showQuickJumper
current={current}
pageSize={pages.limit}
total={pages.total}
onChange={handlePageChange}
/>
</div>
</div>
)
}
const mapStateToProps = (state) => {
const {
commentLists, // 评论列表
pages
} = state.commentReducer;
const {
comment_identifier
} = state.ojForUserReducer;
const { userInfo } = state.userReducer;
return {
commentLists,
isAdmin: userInfo.admin,
identifier: comment_identifier,
pages
}
}
const mapDispatchToProps = (dispatch) => ({
// getCommentLists: (identifier) => dispatch(action.getCommentLists(identifier))
getCommentLists: (identifier) => dispatch(actions.getCommentLists(identifier)),
addComment: (identifier, comments) => dispatch(actions.addComment(identifier, comments)),
replayChildComment: (identifier, comment) => dispatch(actions.replayChildComment(identifier, comment)),
deleteComment: (identifier, id) => dispatch(actions.deleteComment(identifier, id)),
likeComment: (identifier, id, params) => dispatch(actions.likeComment(identifier, id, params)),
showOrHideComment: (identifier, id, params) => dispatch(actions.showOrHideComment(identifier, id, params)),
changePagination: (page) => dispatch(actions.changePagination(page))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(CommentTask);

View File

@@ -0,0 +1,16 @@
.task_comment_task{
display: flex;
flex-direction: column;
justify-content: space-between;
background: #fff;
padding: 0px 20px 0;
height: calc(100vh - 177px);
box-sizing: border-box;
overflow-y: auto;
border-bottom: 1px solid rgba(244,244,244,1);
.task_comment_page{
padding: 10px 0;
text-align: center;
}
}

View File

@@ -0,0 +1,288 @@
/*
* @Description: 提交记录
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 09:49:33
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-09 14:06:34
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
import { Table, Icon, message, Pagination } from 'antd';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import actions from '../../../../../redux/actions';
import CONST from '../../../../../constants';
import moment from 'moment';
import ClipboardJS from 'clipboard';
import ErrorResult from '../../../components/errorResult';
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, record) => (
<Link to={`/myproblems/record_detail/${record.id}`}>
<span style={{ color: value === 0 ? '#28BD8B' : '#E6262E'}}>{reviewResult[value]}</span>
</Link>
)
},
{
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: 5, // 每页显示条数
// current: 1, // 当前页数
// showQuickJumper: true
// }
const CommitRecord = (props) => {
const {
identifier,
pages,
commitRecord,
// excuteState,
language,
operateType,
commitRecordDetail,
getUserCommitRecord,
changeRecordPagination
} = props;
const [current, setCurrent] = useState(1);
// const [pagination, setPagination] = useState(paginationConfig);
// const [tableData, setTableData] = useState([]);
// 复制面板
let clipboard;
// const [recordDetail, setRecordDetail] = useState({});
// const [renderCtx, setRenderCtx] = useState(() => {
// return function () {
// return '';
// }
// });
// 渲染提交记录详情
const renderRecordDetail = (commitRecordDetail = {}) => {
const {id, status} = commitRecordDetail;
if (Object.keys(commitRecordDetail).length > 0) {
// console.log('当前状态====》》》', status);
const classes = status === 0 ? 'record_result_suc' : 'record_result_err';
const showErrorCode = status !== 0 ? `ecord_error_info show_error_code` : `ecord_error_info`;
const showErrorCopy = status !== 0 ? `copy_error show_error_copy` : `copy_error`;
return (
<React.Fragment>
<div className={'record_header'}>
<p className={'record_result'}>
执行结果: <span className={classes}>{reviewResult[status]}</span>
</p>
<p
id="copyError"
onClick={clickCopyErrInfo}
className={showErrorCopy} data-clipboard-target="#errcode">
<span>
复制错误信息 <Icon type="copy" className={'icon_style'}/>
</span>
</p>
<p className={'show_detail'} style={{ visibility: id ? 'visible' : 'hidden' }}>
<Link to={`/myproblems/record_detail/${id}`}>
显示详情 <Icon type="right" className={'icon_style'}/>
</Link>
</p>
</div>
<div id="errcode" className={showErrorCode}>
<ErrorResult detail={commitRecordDetail} language={language}/>
</div>
</React.Fragment>
);
} else {
return '';
}
}
// 根据id获取用户提交记录
useEffect(() => {
getUserCommitRecord(identifier);
}, []);
// 提交记录变化时,同步到表单数据
// useEffect(() => {
// // const len = commitRecord.length;
// // const pageConfig = Object.assign({}, paginationConfig, {total: len});
// setTableData(commitRecord);
// // setPagination(pageConfig);
// }, [commitRecord]);
// 提交详情变化时,显示当前提交信息
// useEffect(() => {
// // setRecordDetail(commitRecordDetail);
// if (operateType === 'submit') {
// setRenderCtx(() => (renderRecordDetail))
// }
// }, [commitRecordDetail, operateType]);
// 复制功能
let count = 0;
// useEffect(() => {
// }, []);
const clickCopyErrInfo = () => {
count = 0;
if (!clipboard) {
console.log('==========>>>>>>>', 11111111111);
clipboard = new ClipboardJS('#copyError');
}
clipboard.on('success', (e) => {
e.clearSelection();
if (count > 0) return;
count++;
message.success('复制成功');
setTimeout(() => {
message.destroy();
}, 3000);
});
}
const handlePaginationChange = (page) => {
setCurrent(page);
changeRecordPagination(page);
// 调用查询接口
getUserCommitRecord(identifier);
// setPagination(Object.assign({}, pagination, { current: page}));
// console.log('======>>>>>>', pagination)
}
// console.log(commitRecord);
const _style = {
display: pages.total > pages.limit ? 'block' : 'none'
};
const {status, id} = commitRecordDetail || {};
const classes = status === 0 ? 'record_result_suc' : 'record_result_err';
const showErrorCode = status !== 0 ? `ecord_error_info show_error_code` : `ecord_error_info`;
const showErrorCopy = status !== 0 ? `copy_error show_error_copy` : `copy_error`;
// if (!clipboard) {
// console.log('==========>>>>>>>', 11111111111);
// clipboard = new ClipboardJS('#copyError');
// }
// clipboard.on('success', (e) => {
// e.clearSelection();
// // if (count > 0) return;
// // count++;
// message.success('复制成功');
// setTimeout(() => {
// message.destroy();
// }, 3000);
// });
// return () => {
// clipboard = null;
// }
return (
<div className={'commit_record_area'}>
{renderRecordDetail(commitRecordDetail)}
{/* <div className={'record_header'}>
<p className={'record_result'}>
执行结果: <span className={classes}>{reviewResult[status]}</span>
</p>
<p
id="copyError"
onClick={clickCopyErrInfo}
className={showErrorCopy} data-clipboard-target="#errcode">
<span>
复制错误信息 <Icon type="copy" className={'icon_style'}/>
</span>
</p>
<p className={'show_detail'} style={{ visibility: id ? 'visible' : 'hidden' }}>
<Link to={`/myproblems/record_detail/${id}`}>
显示详情 <Icon type="right" className={'icon_style'}/>
</Link>
</p>
</div>
<div id="errcode" className={showErrorCode}>
<ErrorResult detail={commitRecordDetail} language={language}/>
</div> */}
<div className="commit_record_table_pagination">
<Table
columns={columns}
rowKey={function (record) { return `key_${record.id}`}}
dataSource={commitRecord}
// pagination={pagination}
// onChange={handleTableChange}
pagination={false}
/>
<div className="commit_record_pagination" style={_style}>
<Pagination
showQuickJumper
pageSize={pages.limit}
current={current}
total={pages.total}
onChange={handlePaginationChange}
/>
</div>
</div>
</div>
)
}
const mapStateToProps = (state) => {
const {
ojForUserReducer,
commonReducer
} = state;
const {
user_program_identifier,
commitRecordDetail,
commitRecord,
hack,
operateType,
pages
} = ojForUserReducer;
const { excuteState } = commonReducer;
return {
identifier: user_program_identifier,
commitRecordDetail,
commitRecord, // 提交记录
excuteState, // 代码执行状态
language: hack.language,
operateType,
pages
}
}
const mapDispatchToProps = (dispatch) => ({
getUserCommitRecord: (identifier) => dispatch(actions.getUserCommitRecord(identifier)),
changeRecordPagination: (page) => dispatch(actions.changeRecordPagination(page))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(CommitRecord);

View File

@@ -0,0 +1,70 @@
.commit_record_area{
// padding: 20px 30px;
padding: 0 20px;
overflow-y: auto;
// height: calc(100vh - 177px);
height: calc(100vh - 122px);
.commit_record_table_pagination{
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.commit_record_pagination{
padding: 10px 0;
text-align: center;
}
}
.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{
visibility: hidden;
text-align: right;
flex: 1;
padding-right: 40px;
&.show_error_copy{
visibility: visible;
}
}
.copy_error,
.show_detail{
color: #5091FF;
font-size: 14px;
cursor: pointer;
}
.icon_style{
font-size: 12px;
margin-left: 2px;
cursor: pointer;
}
.record_result_suc{
color: #28BD8B;
}
.record_result_err{
color: #E51C24;
}
}
.record_error_info{
display: none;
padding: 20px 30px;
word-wrap:break-word;
}
.show_error_code{
display: block;
background: rgba(250,250,250,1);
// padding: 20px 30px;
color: #E51C24;
margin-bottom: 20px;
}
}

View File

@@ -0,0 +1,164 @@
/*
* @Description: 学员测评页面
* @Author: tangjiang
* @Github:
* @Date: 2019-11-23 11:33:41
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-02 13:51:22
// */
import './index.scss';
import React, { useState, useEffect, useMemo } from 'react';
import { 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';
import CommentForm from '../../../../common/components/comment/CommentForm';
// const { TabPane } = Tabs;
const LeftPane = (props) => {
const { hack, userCodeTab } = props;
const {
pass_count,
submit_count,
praises_count, /* 点赞数 */
comments_count, /* 评论数*/
user_praise // 用户是否点赞
} = hack;
const [defaultActiveKey, setDefaultActiveKey] = useState('task');
const navItem = [
{
title: '任务描述',
key: 'task'
},
{
title: '提交记录',
key: 'record'
},
{
title: '评论',
key: 'comment'
}
];
const Comp = {
task: (<TaskDescription />),
record: (<CommitRecord />),
comment: (<Comment />)
};
useEffect(() => {
setDefaultActiveKey(userCodeTab);
}, [userCodeTab])
const renderComp = useMemo(() => {
return Comp[defaultActiveKey];
}, [defaultActiveKey, setDefaultActiveKey]);
const renderNavItem = navItem.map((item) => {
const _classes = item.key === defaultActiveKey ? 'add_editor_item active' : 'add_editor_item';
return (
<li
key={item.key}
className={_classes}
onClick={() => setDefaultActiveKey(item.key)}
>
<span className={'item-span'}>{item.title}</span>
</li>
)
});
// 点击消息
const handleClickMessage = () => {
// 切换到评论tab
setDefaultActiveKey('comment');
}
// 点击点赞
const handleClickLike = () => {
// 对OJ进行点赞
const {id, identifier } = props.hack;
props.likeComment(identifier, id, {
container_type: 'Hack',
type: 1
});
};
// 点击不喜欢
// const handleClickDisLike = () => {
// console.log('点击的DisLike---------');
// }
// 添加评论
const handleAddComment = (ctx) => {
// console.log('添加的评论内容: ', ctx, props.identifier);
props.identifier && props.addComment(props.identifier, {
comments: {
content: ctx
}
});
};
const _style = {
display: defaultActiveKey === 'record' ? 'none' : 'flex'
};
return (
<React.Fragment>
<ul className={'add_editor_list_area'}>
{ renderNavItem }
</ul>
<div className="comp_ctx">
{ renderComp }
</div>
<div className={'number_area'} style={_style}>
<div className="number_flex flex_count" style={{ display: defaultActiveKey !== 'comment' ? 'flex' : 'none'}}>
<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_quill" style={{ display: defaultActiveKey === 'comment' ? 'flex' : 'none'}}>
<CommentForm
onSubmit={handleAddComment}
type="bottom"
/>
</div>
<div className="number_flex flex_info">
<TextNumber text="huifu1" number={hack.comments_count} type="icon" onIconClick={handleClickMessage}/>
<TextNumber
className={user_praise ? 'like active' : 'like'}
text="dianzan"
number={praises_count}
theme={user_praise ? 'filled' : ''}
type="icon"
onIconClick={handleClickLike}/>
{/* <TextNumber text="dislike" number={0} type="icon" onIconClick={handleClickDisLike}/> */}
</div>
</div>
</React.Fragment>
);
}
const mapStateToProps = (state) => {
const { hack, userCodeTab, comment_identifier} = state.ojForUserReducer;
return {
hack,
userCodeTab,
identifier: comment_identifier
}
}
// changeUserCodeTab
const mapDispatchToProps = (dispatch) => ({
changeUserCodeTab: (key) => dispatch(actions.changeUserCodeTab(key)),
likeComment: (identifier, id, params) => dispatch(actions.likeComment(identifier, id, params)),
addComment: (identifier, comments) => dispatch(actions.addComment(identifier, comments))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(LeftPane);

View File

@@ -0,0 +1,118 @@
@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%;
box-sizing: border-box;
border-top: 1px solid rgba(244,244,244,1);
// background: pink;
padding: 0 20px;
// background-color: rgba(250,250,250,1);
background: #fff;
.flex_count,
.flex_info,
.flex_quill{
display: flex;
flex-direction: row;
justify-content: space-between;
}
.flex_info{
// width: 140px;
justify-content: flex-end;
.like{
margin-left: 20px
}
.like.active{
.numb_icon{
color: #5091FF;
}
}
}
.flex_quill{
// position: relative;
flex: 1;
align-items: center;
height: 100%;
top: 10px;
margin-right: 20px;
}
}
.commit_record_area{
padding: 0 20px;
background: #fff;
// height: calc(100vh - 178px);
}
.task_description_area{
height: calc(100vh - 177px);
.task_desc_area{
height: calc(100vh - 242px);
overflow-y: auto;
// padding: 0 0 0 15px;
background: #fff;
}
.desc_area_header{
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;
padding: 0 20px;
background: #fff;
box-sizing: border-box;
border-bottom: 1px solid #f4f4f4;
.header_flex{
font-size: 14px;
.flex_label{
color: #888888;
margin-right: 10px;
}
.flex_value{
font-weight: 500;
color: #333;
// color: green;
}
}
}
}
.student_study_header{
.study_quit{
position: absolute;
right: 20px;
}
.quit-btn{
cursor: pointer;
margin-left: 30px;
color: #888888;
transition: all .3s;
&:hover{
color: #5091FF;
}
// &:last-child{
// color: #888888;
// }
.quit-icon{
margin-right: 5px;
}
}
}
.add_editor_list_area{
box-sizing: border-box;
border-bottom: 1px solid rgba(244,244,244,1);
}

View File

@@ -0,0 +1,64 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 09:49:30
* @LastEditors : tangjiang
* @LastEditTime : 2019-12-27 20:22:55
*/
import '../index.scss';
import React from 'react';
import { Tag } from 'antd';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import QuillForEditor from '../../../../../common/quillForEditor';
import CONST from '../../../../../constants';
const {tagBackground, diffText} = CONST;
const TaskDescription = (props) => {
const { hack = {}, userInfo = {} } = 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={hack.user_path || '/'} target="_blank" style={{ color: '#5091FF'}}>{username}</Link>
</p>
</div>
<div className="task_desc_area">
<QuillForEditor
readOnly={true}
value={description}
/>
</div>
</div>
)
}
const mapStateToProps = (state) => {
const { hack } = state.ojForUserReducer;
const { userInfo } = state.userReducer;
return {
hack,
userInfo
}
}
export default connect(
mapStateToProps
)(TaskDescription);

View File

@@ -0,0 +1,220 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2019-11-27 14:59:51
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-02 14:23:43
*/
import React, { useState, useEffect } from 'react';
import {connect} from 'react-redux';
import MyMonacoEditor from '../../components/myMonacoEditor';
import ControlSetting from '../../components/controlSetting';
import actions from '../../../../redux/actions';
// import QuillForEditor from '../../../../common/quillForEditor';
// import TextArea from 'antd/lib/input/TextArea';
import { Input, Form, Button } from 'antd';
// import FormItem from 'antd/lib/form/FormItem';
const { TextArea } = Input;
const FormItem = Form.Item;
const RightPane = (props) => {
const {
identifier,
submitInput,
submitUserCode,
input,
hack,
loading,
notice,
updateCode,
hadCodeUpdate,
editor_code,
updateNotice,
saveUserInputCode,
restoreInitialCode,
// saveOpacityType,
saveUserCodeForInterval,
addNotes,
changeLoadingState
} = props;
// const [editorCode, setEditorCode] = useState(editor_code || hack.code);
const [noteClazz, setNoteClazz] = useState('editor_nodte_area');
const [noteCount] = useState(5000);
// const [code, setCode] = useState(editor_code || hack.code);
// let initFlag = true;
// useEffect(() => {
// if (editor_code) {
// setEditorCode(editor_code);
// } else {
// setEditorCode(hack.code);
// }
// }, [hack, editor_code]);
const handleSubmitForm = () => {
// 提交时, 先调用提交接口,提交成功后,循环调用测评接口
// saveOpacityType('submit');
submitUserCode(identifier, submitInput, 'submit');
// // 提交时,先调用评测接口, 评测通过后才调用保存接口
// updateCode(identifier, submitInput, 'submit');
}
let timer = null; // 定时器
// 代码块内容变化时
const handleCodeChange = (value) => {
// console.log('编辑器代码 ======》》》》》》》》》++++++++++', value);
saveUserInputCode(value);
// setEditorCode(value);
if (!timer) {
timer = setInterval(function () {
clearInterval(timer);
timer = null;
saveUserCodeForInterval(identifier);
}, 3000);
}
}
// 代码调试
const handleDebuggerCode = (value) => {
// 调用保存代码块接口,成功后,调用调试接口
// saveOpacityType('debug');
updateCode(identifier, value, 'debug');
}
// 恢复初始代码
const handleRestoreInitialCode = () => {
restoreInitialCode(identifier, '恢复初始代码成功');
}
// 更新代码
const handleUpdateNotice = () => {
updateNotice && updateNotice();
};
const handleClickNote = () => {
setNoteClazz('editor_nodte_area active');
}
const handleCancelNote = () => {
props.form.resetFields();
setNoteClazz('editor_nodte_area');
}
const handleSubmitNote = () => {
props.form.validateFields((err, values) => {
if (!err) {
changeLoadingState(true);
addNotes(identifier, values, function () {
setNoteClazz('editor_nodte_area');
props.form.resetFields();
});
}
});
}
const { getFieldDecorator } = props.form;
return (
<div className={'right_pane_code_wrap'}>
<MyMonacoEditor
notice={notice}
identifier={identifier}
language={hack.language}
code={editor_code || hack.code}
hadCodeUpdate={hadCodeUpdate}
onCodeChange={handleCodeChange}
onUpdateNotice={handleUpdateNotice}
onRestoreInitialCode={handleRestoreInitialCode}
/>
<span
className="iconfont icon-biji student_notes"
onClick={handleClickNote}
></span>
{/* <div className="student_notes">
<TextArea rows={5} />
</div> */}
<div className={noteClazz}>
<Form>
<FormItem>
{
getFieldDecorator('notes',{
rules: [
{ required: true, message: '笔记不能为空' },
{ max: noteCount, message: `笔记最大字数为${noteCount}` }
],
initialValue: (hack && hack.notes) || ''
})(<TextArea
max={noteCount}
placeholder="请输入笔记内容"
rows="5"
/>)
}
</FormItem>
<FormItem style={{ textAlign: 'right' }}>
<Button loading={loading} style={{ marginRight: '10px' }} onClick={handleCancelNote}>取消</Button>
<Button type="primary" onClick={handleSubmitNote}>提交</Button>
</FormItem>
</Form>
</div>
<ControlSetting
identifier={identifier}
inputValue={input}
onDebuggerCode={handleDebuggerCode}
onSubmitForm={handleSubmitForm}/>
</div>
);
}
const mapStateToProps = (state) => {
const {
user_program_identifier,
hack,
userTestInput,
editor_code,
notice,
hadCodeUpdate
} = state.ojForUserReducer;
const {
loading
} = state.commonReducer;
// const { language, code } = hack;
return {
hack,
notice,
loading,
hadCodeUpdate,
editor_code,
input: userTestInput,
submitInput: hack.input,
identifier: user_program_identifier
};
}
const mapDispatchToProps = (dispatch) => ({
// type: 提交类型 debug | submit
submitUserCode: (identifier, inputValue, type) => dispatch(actions.submitUserCode(identifier, inputValue, type)),
// 更新代码块内容
updateCode: (identifier, inputValue, type) => dispatch(actions.updateCode(identifier, inputValue, type)),
// 保存用户代码块至Reducer中
saveUserInputCode: (code) => dispatch(actions.saveUserInputCode(code)),
// 保存用户代码至后台
saveUserCodeForInterval: (identifier, code) => dispatch(actions.saveUserCodeForInterval(identifier, code)),
// 恢复初始代码
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
// saveOpacityType: (type) => dispatch(actions.saveOpacityType(type))
// 添加笔记
addNotes: (identifier, params, cb) => dispatch(actions.addNotes(identifier, params, cb)),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form.create()(RightPane));