mirror of
https://gitlink.org.cn/Gitlink/forgeplus.git
synced 2026-05-04 04:03:25 +08:00
init project
This commit is contained in:
589
public/react/src/modules/developer/DeveloperHome.js
Normal file
589
public/react/src/modules/developer/DeveloperHome.js
Normal 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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -0,0 +1,47 @@
|
||||
.excute_result_area{
|
||||
display: flex;
|
||||
height: 224px;
|
||||
width: 100%;
|
||||
|
||||
&.excute_flex_center{
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.init_ctx{
|
||||
color: #666666;
|
||||
}
|
||||
.loading_ctx,
|
||||
.loaded_ctx{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #1890ff;
|
||||
.ctx_icon{
|
||||
font-size: 40px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.excute_result_info{
|
||||
padding: 20px 30px;
|
||||
color: #fff;
|
||||
height: 220px;
|
||||
/* overflow-y: auto; */
|
||||
overflow-y: auto;
|
||||
|
||||
.result_info_style{
|
||||
word-wrap: break-word;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.excute_head_area{
|
||||
line-height: 30px;
|
||||
.excute_suc{
|
||||
color: #28BD8B;
|
||||
}
|
||||
.excute_err{
|
||||
color: #E51C24;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
194
public/react/src/modules/developer/components/knowledge/index.js
Normal file
194
public/react/src/modules/developer/components/knowledge/index.js
Normal 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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
19
public/react/src/modules/developer/index.js
Normal file
19
public/react/src/modules/developer/index.js
Normal 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));
|
||||
88
public/react/src/modules/developer/index.scss
Normal file
88
public/react/src/modules/developer/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
287
public/react/src/modules/developer/newOrEditTask/index.js
Normal file
287
public/react/src/modules/developer/newOrEditTask/index.js
Normal 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)));
|
||||
32
public/react/src/modules/developer/newOrEditTask/index.scss
Normal file
32
public/react/src/modules/developer/newOrEditTask/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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)));
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
// }
|
||||
}
|
||||
@@ -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)
|
||||
));
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
@import '../../split_pane_resizer.scss';
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* @Description: 显示tab中的内容
|
||||
* @Author: tangjiang
|
||||
* @Date: 2019-11-18 10:43:03
|
||||
* @Last Modified by: tangjiang
|
||||
* @Last Modified time: 2019-11-18 11:35:12
|
||||
*/
|
||||
import './index.scss';
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Icon, Form, Input } from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import actions from '../../../../../redux/actions';
|
||||
const FormItem = Form.Item;
|
||||
const { TextArea } = Input;
|
||||
const tabCtx = (ctx, props) => (<p {...props}>{ctx}</p>);
|
||||
const renderUserCase = (ctx, position, props) => {
|
||||
const {form: { getFieldDecorator }, testCases = []} = props;
|
||||
const testCase = testCases[0] || {}; // 获取第一个测试用例
|
||||
return (
|
||||
<Form className={'user_case_form'}>
|
||||
<FormItem
|
||||
className={'input_area flex_l'}
|
||||
label='输入'
|
||||
>
|
||||
{
|
||||
getFieldDecorator('input', {
|
||||
rules: [
|
||||
{ required: true, message: '输入值不能为空'}
|
||||
],
|
||||
initialValue: testCase.input
|
||||
})(<TextArea rows={5} />)
|
||||
}
|
||||
</FormItem>
|
||||
{/* <FormItem
|
||||
className={'input_area flex_r'}
|
||||
label="输出">
|
||||
{
|
||||
getFieldDecorator('output', {
|
||||
rules: [
|
||||
{required: true, message: '输出值不能为空'}
|
||||
],
|
||||
initialValue: testCase.output
|
||||
})(<Input />)
|
||||
}
|
||||
</FormItem> */}
|
||||
</Form>
|
||||
)
|
||||
};
|
||||
const defaultCtx = (<span className={'ctx_default'}>请在这里添加测试用例,点击“调试代码”时将从这里读取输入来测试你的代码...</span>)
|
||||
const loadingCtx = (<span className={'ctx_loading'}><Icon className={'ctx_icon'} type="loading"/>加载中...</span>);
|
||||
const loadedCtx = (<span className={'ctx_loaded'}><Icon className={'ctx_icon'} type="loading"/>加载完成</span>);
|
||||
const maps = {
|
||||
// default: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_default pos_${position}`}>{ctx}</p>),
|
||||
// loading: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_loading pos_${position}`}>{ctx}</p>),
|
||||
// loaded: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_loaded pos_${position}`}>{ctx}</p>),
|
||||
// final: (ctx, position) => (<p className={`tab_ctx_area tab_ctx_final pos_${position}`}>{ctx}</p>)
|
||||
// 无测试用例时,显示提示信息, ctx: 显示的信息, position: 显示信息的位置
|
||||
default: (ctx, position) => tabCtx(defaultCtx, { className: `tab_ctx_area tab_ctx_default pos_${position}` }),
|
||||
// 调度代码加载中
|
||||
loading: (ctx, position) => tabCtx(loadingCtx, { className: `tab_ctx_area tab_ctx_loading pos_${position}` }),
|
||||
// 调度代码加载完成
|
||||
loaded: (ctx, position) => tabCtx(loadedCtx, { className: `tab_ctx_area tab_ctx_loaded pos_${position}` }),
|
||||
// 显示结果
|
||||
final: (ctx, position) => tabCtx(ctx, { className: `tab_ctx_area tab_ctx_final pos_${position}` }),
|
||||
// 显示自定义测试用例面板
|
||||
userCase: (ctx, position, props) => renderUserCase(ctx, position, props)
|
||||
}
|
||||
|
||||
class InitTabCtx extends PureComponent {
|
||||
|
||||
state = {
|
||||
ctx: '',
|
||||
position: ''
|
||||
}
|
||||
|
||||
handleTestCodeFormSubmit = (cb) => {
|
||||
const {form, debuggerCode} = this.props;
|
||||
// console.log(debuggerCode);
|
||||
form.validateFields((err, values) => {
|
||||
if (!err) { // 表单验证通过时,调用测试接口
|
||||
cb && cb(); // 调用回调函数,切换 tab
|
||||
// console.log('表单值:', values);
|
||||
debuggerCode(values);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { testCases = []} = this.props;
|
||||
this.setState({
|
||||
status: testCases.length > 0 ? 'userCase' : 'default'
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
/**
|
||||
* @param state 当前状态 default: 显示提示信息 init: 加载初始内容 loading: 加载中 loaded: 加载完成 final: 显示最终内容
|
||||
* @param position: start | cetner | end
|
||||
* @param testCase: 自定义测试用例
|
||||
* @returns
|
||||
*/
|
||||
const { testCodeStatus} = this.props;
|
||||
const { ctx, position } = this.state;
|
||||
// console.log('===>>>>> 测试用例集合: ', testCases);
|
||||
return(
|
||||
<React.Fragment>
|
||||
{ maps[testCodeStatus](ctx, position, this.props) }
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const ojFormReducer = state.ojFormReducer;
|
||||
return {
|
||||
testCases: ojFormReducer.testCases, // 测试用例
|
||||
testCodeStatus: ojFormReducer.testCodeStatus
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
debuggerCode: (value) => dispatch(actions.debuggerCode(value))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Form.create()(InitTabCtx));
|
||||
@@ -0,0 +1,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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
183
public/react/src/modules/developer/quillEditor/README.md
Normal file
183
public/react/src/modules/developer/quillEditor/README.md
Normal 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');
|
||||
134
public/react/src/modules/developer/quillEditor/index.js
Normal file
134
public/react/src/modules/developer/quillEditor/index.js
Normal 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;
|
||||
164
public/react/src/modules/developer/recordDetail/index.js
Normal file
164
public/react/src/modules/developer/recordDetail/index.js
Normal 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));
|
||||
55
public/react/src/modules/developer/recordDetail/index.scss
Normal file
55
public/react/src/modules/developer/recordDetail/index.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
258
public/react/src/modules/developer/split_pane_resizer.scss
Normal file
258
public/react/src/modules/developer/split_pane_resizer.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
193
public/react/src/modules/developer/studentStudy/index.js
Normal file
193
public/react/src/modules/developer/studentStudy/index.js
Normal 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)));
|
||||
|
||||
|
||||
56
public/react/src/modules/developer/studentStudy/index.scss
Normal file
56
public/react/src/modules/developer/studentStudy/index.scss
Normal 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%;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
Reference in New Issue
Block a user