dev_tj
tangjiang 5 years ago
parent 3770b63a0a
commit 08012fb515

@ -366,6 +366,11 @@ const JupyterTPI = Loadable({
loader: () => import('./modules/tpm/jupyter'), loader: () => import('./modules/tpm/jupyter'),
loading: Loading loading: Loading
}); });
// 微信代码编辑器
const WXCode = Loadable({
loader: () => import('./modules/wxcode'),
loading: Loading
});
// //个人竞赛报名 // //个人竞赛报名
// const PersonalCompetit = Loadable({ // const PersonalCompetit = Loadable({
// loader: () => import('./modules/competition/personal/PersonalCompetit.js'), // loader: () => import('./modules/competition/personal/PersonalCompetit.js'),
@ -823,6 +828,11 @@ class App extends Component {
render={ render={
(props) => (<Headplugselection {...this.props} {...props} {...this.state} />) (props) => (<Headplugselection {...this.props} {...props} {...this.state} />)
}/> }/>
<Route path="/wxcode/:identifier?" component={WXCode}
render={
(props)=>(<WXCode {...this.props} {...props} {...this.state}></WXCode>)
}
/>
<Route exact path="/" <Route exact path="/"
// component={ShixunsHome} // component={ShixunsHome}
render={ render={

@ -0,0 +1,271 @@
/*
* @Description: 微信端代码编辑器
* @Author: tangjiang
* @Github:
* @Date: 2020-01-15 09:56:34
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-16 15:22:25
*/
import './index.scss';
import React, {useState, useEffect, useRef} from 'react';
import MonacoEditor from '@monaco-editor/react';
import { Input, Icon } from 'antd';
import { connect } from 'react-redux';
import actions from '../../redux/actions';
const { TextArea } = Input;
const App = (props) => {
const {
isShow,
wxCode,
path,
showLoading,
// userCode,
testCase = [],
getWXCode,
last_compile_output,
test_sets_count,
sets_error_count,
getWXCodeTestCase,
restoreWXCode,
updateWXCodeForEditor,
updateWXCodeForInterval,
evaluateWxCode,
showWXCodeTextCase,
changeWXCodeEvaluateLoading
} = props;
const {identifier} = props.match.params;
const [isActive, setIsActive] = useState(-1);
// const [isVisible, setIsVisible] = useState(false);
const editorRef = useRef(null);
let timer = null;
useEffect(() => {
// 加载代码块内容
getWXCode(identifier);
// 加载测试集
const params = {
path,
status: 0,
retry: 1
};
getWXCodeTestCase(identifier, params);
}, []);
// 关闭
const handleCloseTestCase = () => {
// setIsVisible(false);
showWXCodeTextCase(false)
}
// 测试集
const handleClickTestCase = () => {
// setIsVisible(true);
showWXCodeTextCase(true)
}
// 编辑器代码
const handleEditorChange = (origin, monaco) => {
editorRef.current = monaco; // 获取当前monaco实例
// setEditCode(origin); // 保存编辑器初始值
editorRef.current.onDidChangeModelContent(e => { // 监听编辑器内容的变化
// TODO 需要优化 节流
const val = editorRef.current.getValue();
// console.log('编辑器代码====>>>>', val);
// updateWXCodeForEditor(val);
codeChange(val);
});
};
const codeChange = (code) => {
// console.log(code);
updateWXCodeForEditor(code);
if (!timer) {
timer = setInterval(function () {
clearInterval(timer);
timer = null;
// 调用更新代码
updateWXCodeForInterval(identifier, path);
}, 10000);
}
}
// 关闭单个测试集
const handleCloseItem = (i, flag) => {
if (!flag) return;
setIsActive(isActive === i ? -1 : i);
}
// 初始化
const handleResetCode = () => {
const result = window.confirm('你在本文件中修改的内容将丢失, 是否确定重新加载初始代码?');
if (result) {
identifier && restoreWXCode(identifier, { path });
}
}
// 评测
const handleEvalateCode = () => {
changeWXCodeEvaluateLoading(true);
evaluateWxCode(identifier, path);
}
const tcclasses = isShow ? `wx-code-test-case active` : 'wx-code-test-case';
const loading = showLoading ? 'code-evaluate-loading active' : 'code-evaluate-loading';
const _val = sets_error_count === 0;
let resultTxt = (_val) ? '全部通过' : `${sets_error_count}组测试结果不匹配`;
const iclasses = _val ? 'iconfont icon-tishi1 icon success' : 'iconfont icon-tishi1 icon fail';
const tclasses = _val ? 'result-txt success' : 'result-txt fail';
const ulClasses = last_compile_output ? 'case-list hasResult' : 'case-list';
return (
<div className="wx-code-area">
<div className="wx-code-flex">
<div className="wx-code-item">
<MonacoEditor
height="100%"
width="100%"
language="python"
value={wxCode}
options={{
selectOnLineNumbers: true,
automaticLayout: true,
fontSize: `32px`
}}
theme='dark'
editorDidMount={handleEditorChange}
/>
</div>
<div className="wx-code-test">
<div className="flex-btn">
<span className="icon-btn" onClick={handleResetCode}>
<i className="iconfont icon-reset icon"></i>
<span className="icon-txt">初始化</span>
</span>
<span className="icon-btn" onClick={handleClickTestCase}>
<i className="iconfont icon-base icon"></i>
<span className="icon-txt">测试集</span>
</span>
</div>
{/* <Button type="primary" shape="circle">评测</Button> */}
<button className="wx-pt-btn" onClick={handleEvalateCode}>评测</button>
</div>
</div>
{/* 测试集 */}
<div className={tcclasses}>
<div className="text-case-list">
<div className="list-header">
<span className="header-title">{testCase.length}个测试用例</span>
<span className="header-close" onClick={handleCloseTestCase}>关闭</span>
</div>
<div className="wxcode-test-result" style={{ display: last_compile_output ? 'block' : 'none'}}>
<i className={iclasses}></i>
<span className={tclasses}>{test_sets_count - sets_error_count}/{test_sets_count}</span>
<span className={`${tclasses} result-txt-desc`}>{resultTxt}</span>
</div>
<ul className={ulClasses}>
{
testCase.map((item, i) => {
const {input, output, actual_output, is_public, result} = item;
const _classes = isActive === i ? 'case-item-desc active' : 'case-item-desc';
const iconclasses = isActive === i ? 'iconfont icon-sanjiaoxing-down icon active' : 'iconfont icon-triangle icon';
const headerClasses = is_public ? 'item-header-desc active' : 'item-header-desc';
// console.log(_classes);
return (
<li className="case-item" key={`item_${i}`}>
<div className="case-item-header" onClick={() => handleCloseItem(i, is_public)}>
<h2 className={headerClasses}>
<i className={iconclasses}></i>
测试集{i + 1}
</h2>
{
is_public
? (result
? <span className="iconfont icon-wancheng case_item_success"></span>
: <span className="iconfont icon-jinggao1 case_item_fail"></span>)
: (<span className="case-item-tips">
隐藏测试集暂不支持解锁和查看
{/* {result
? <span className="iconfont icon-wancheng case_item_success"></span>
: <span className="iconfont icon-jinggao1 case_item_fail"></span>
} */}
</span>)
}
</div>
<div className={_classes}>
<span className="desc-title">测试输入</span>
<span className="test-input">{input || '-'}</span>
<span className="desc-title">预期输出</span>
{/* <textarea rows="5">预期输出</textarea> */}
<TextArea
readOnly={true}
className="text-area-style"
value={output}
onChange={this.onChange}
placeholder="Controlled autosize"
autoSize={{ minRows: 3, maxRows: 6 }}
/>
{/* <TextArea rows={5} className="hope-result">预期输出</TextArea> */}
<span className="desc-title">实际输出</span>
<TextArea
readOnly={true}
className="text-area-style"
value={actual_output}
autoSize={{ minRows: 1, maxRows: 3 }}
/>
</div>
</li>
)
})
}
</ul>
</div>
</div>
{/* 测评中 */}
<div className={loading}>
<span className="loading-flex">
<Icon className="loading-icon" type="loading" />
<span className="loading-txt">测评中...</span>
</span>
</div>
</div>
);
}
const mapStateToProps = (state) => {
const {
path,
isShow,
wxCode,
userCode,
testCase,
showLoading,
last_compile_output,
test_sets_count,
sets_error_count
} = state.wxcodeReducer;
console.log(state);
return {
path,
isShow,
wxCode,
userCode,
testCase,
showLoading,
last_compile_output,
test_sets_count,
sets_error_count
};
}
const mapDispatchToProps = (dispatch) => ({
getWXCode: (identifier) => dispatch(actions.getWXCode(identifier)),
getWXCodeTestCase: (identifier, params) => dispatch(actions.getWXCodeTestCase(identifier, params)),
restoreWXCode: (identifier, params) => dispatch(actions.restoreWXCode(identifier, params)),
updateWXCodeForEditor: (code) => dispatch(actions.updateWXCodeForEditor(code)),
updateWXCodeForInterval: (identifier, path) => dispatch(actions.updateWXCodeForInterval(identifier, path)),
evaluateWxCode: (identifier, path) => dispatch(actions.evaluateWxCode(identifier, path)),
showWXCodeTextCase: (flag) => dispatch(actions.showWXCodeTextCase(flag)),
changeWXCodeEvaluateLoading: (flag) => dispatch(actions.changeWXCodeEvaluateLoading(flag))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);

@ -0,0 +1,275 @@
.wx-code-area{
height: 100vh;
overflow: hidden;
.wx-code-flex{
display: flex;
position: relative;
flex-direction: column;
height: 100%;
}
.wx-code-item{
flex: 1;
}
.wx-code-test{
display: flex;
justify-content: space-between;
align-items: center;
height: 120px;
background-color: #052645;
padding: 0 50px;
}
.flex-btn{
display: flex;
.icon-btn{
display: flex;
flex-direction: column;
color: #2EA4FF;
align-items: center;
.icon{
// font-size: 24px !important;
transform: scale(2);
line-height: 2;
}
.icon-reset{
transform: scale(2.4);
}
// .icon:first-child{
// transform: scale((3));
// }
&:last-child{
margin-left: 50px;
}
.icon-txt{
font-size: 24px;
}
}
}
.wx-pt-btn{
border: none;
outline: none;
border-radius: 9999px;
padding: 0 40px;
line-height: 72px;
font-size: 32px;
color: #fff;
background:#2EA4FF;
}
.wx-code-test-case{
position: fixed;
bottom: 0;
left: 0;
right: 0;
top: 0;
transform: translateY(100%);
transition: transform, opacity .3s;
opacity: 0;
// border-top-left-radius: 16px;
// border-top-right-radius: 16px;
&::before{
position: absolute;
width: 100%;
height: 100%;
content: '';
background: rgba(0,0,0,.6);
}
.text-case-list{
position: absolute;
width: 100%;
bottom: 0;
top: 150px;
background:rgba(1,14,31,1);
border-top-left-radius: 16px;
border-top-right-radius: 16px;
border:2px solid rgba(33,56,87,1);
color: #fff;
}
.list-header{
display: flex;
justify-content: space-between;
align-items: center;
height: 88px;
padding: 30px;
box-sizing: border-box;
border-bottom: 2px solid #213857;
.header-title{
color:#637DA6;
font-size: 24px;
}
.header-close{
font-size: 28px;
color: #2EA4FF;
}
}
.wxcode-test-result{
display: flex;
height: 72px;
// background: gold;
align-items: center;
padding: 0 30px;
.success{
color: rgba(68,209,95,1);
}
.fail{
color: rgba(196, 79, 78, 1);
}
.icon{
font-size: 36px !important;
position: relative;
top: -8px;
margin-right: 10px;
}
.result-txt{
font-size: 34px;
}
.result-txt-desc{
max-width: 500px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
margin: 20px;
}
}
.case-list{
position: absolute;
left: 0;
right: 0;
bottom: 0;
top: 0px;
overflow: auto;
margin-top: 88px;
padding: 0 30px 30px;
// margin: 88px 30px 0;
.case-item{
position: relative;
margin-top: 30px;
border-radius: 8px;
background:rgba(23,39,64,1);
padding: 30px;
}
&.hasResult{
margin-top: 170px;
.case-item:first-child{
margin-top: 0;
}
}
.case-item-header{
display: flex;
justify-content: space-between;
align-items: center;
.case_item_success,
.case_item_fail{
font-size: 24px !important;
transform: scale(2);
}
.case_item_success{
color: rgba(68,209,95,1);
}
.case_item_fail{
color: rgba(196, 79, 78, 1);
}
.case-item-tips{
color: #C67676;
font-size: 22px;
}
}
.item-header-desc{
font-size: 32px;
color: #405D8C;
line-height: 1.5;
// background: gold;
.icon{
position: relative;
top: -4px;
font-size: 32px !important;
margin-right: 10px;
}
&.active{
color: #fff;
}
}
.case-item-tips{
font-size: 28px;
}
.case-item-desc{
display: none;
flex-direction: column;
font-size: 28px;
line-height: 1.5;
&.active{
display: flex;
}
}
.desc-title{
color: #637DA6;
line-height: 2;
}
.text-area-style{
background:#010E1F !important;
color: #fff;
font-size: 28px;
line-height: 1.5;
border: none;
}
}
&.active{
transform: translateY(0);
opacity: 1;
// .item-header-desc{
// color: #fff;
// }
}
}
.code-evaluate-loading{
display: none;
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
justify-content: center;
align-items: center;
&::before{
position: absolute;
width: 100%;
height: 100%;
content: '';
background: rgba(0,0,0,.5);
}
.loading-flex{
display: flex;
position: absolute;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
text-align: center;
color: #fff;
.loading-icon{
font-size: 100px !important;
}
.loading-txt{
font-size: 32px;
}
}
&.active{
display: flex;
}
}
}

@ -92,7 +92,14 @@ const types = {
/** 统计 */ /** 统计 */
GET_STATIC_INFO: 'GET_STATIC_INFO', GET_STATIC_INFO: 'GET_STATIC_INFO',
CHANGE_STATIC_PARAMS: 'CHANGE_STATIC_PARAMS', CHANGE_STATIC_PARAMS: 'CHANGE_STATIC_PARAMS',
CHANGE_STATIC_TOTAL: 'CHANGE_STATIC_TOTAL' CHANGE_STATIC_TOTAL: 'CHANGE_STATIC_TOTAL',
/** 微信 */
GET_WXCODE_BY_IDENTIFIER: 'GET_WXCODE_BY_IDENTIFIER',
GET_WXCODE_TEST_CASE: 'GET_WXCODE_TEST_CASE',
UPDATE_WXCODE_BY_IDENTIFIER: 'UPDATE_WXCODE_BY_IDENTIFIER',
UPDATE_WXCODE_FOR_EDITOR: 'UPDATE_WXCODE_FOR_EDITOR',
IS_SHOW_WXCODE_TEST_CASES: 'IS_SHOW_WXCODE_TEST_CASES',
SHOW_WX_CODE_LOADING: 'SHOW_WX_CODE_LOADING'
} }
export default types; export default types;

@ -12,6 +12,7 @@ import {
changePaginationInfo, changePaginationInfo,
deleteItem deleteItem
} from './ojList'; } from './ojList';
import { import {
validateOjForm, validateOjForm,
saveOjFormCode, saveOjFormCode,
@ -109,6 +110,16 @@ import {
initTotal initTotal
} from './static'; } from './static';
import {
getWXCode,
getWXCodeTestCase,
restoreWXCode,
updateWXCodeForEditor,
updateWXCodeForInterval,
evaluateWxCode,
showWXCodeTextCase,
changeWXCodeEvaluateLoading
} from './wxCode';
export default { export default {
toggleTodo, toggleTodo,
getOJList, getOJList,
@ -191,5 +202,14 @@ export default {
// 统计 // 统计
staticList, staticList,
changeParams, changeParams,
initTotal initTotal,
// 微信
getWXCode,
getWXCodeTestCase,
restoreWXCode,
updateWXCodeForEditor,
updateWXCodeForInterval,
evaluateWxCode,
showWXCodeTextCase,
changeWXCodeEvaluateLoading
} }

@ -0,0 +1,196 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2020-01-15 15:41:10
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-16 15:25:25
*/
import types from './actionTypes.js';
import {
fetchWxCode,
fetchWxCodeTextCase,
fetchRestoreWxCode,
fetchUpdateWxCode,
fetchWxCodeGameBuild,
fetchWxCodeGameStatus
} from '../../services/wxcodeService.js';
// 加载代码块
export const getWXCode = (identifier) => {
return (dispatch) => {
fetchWxCode(identifier).then(res => {
if (res.status === 200) {
dispatch({
type: types.GET_WXCODE_BY_IDENTIFIER,
payload: res.data.content
});
}
});
}
};
// 加载测试集
export const getWXCodeTestCase = (identifier, params) => {
return (dispatch) => {
fetchWxCodeTextCase(identifier, params).then(res => {
// console.log('加载测试集: ====>>>>>>', res);
try{
const {data = {}} = res;
console.log(data.test_sets);
dispatch({
type: types.GET_WXCODE_TEST_CASE,
payload: {
test_sets: data.test_sets || [],
game_id: data.game && data.game.id,
myIdentifier: data.myshixun.identifier,
exec_time: data.challenge.exec_time,
path: data.challenge.path,
last_compile_output: data.last_compile_output,
test_sets_count: data.test_sets_count,
sets_error_count: data.sets_error_count
}
});
} catch(err) {
console.log(err);
};
});
}
}
// 初始化
export const restoreWXCode = (identifier, params) => {
return (dispatch) => {
fetchRestoreWxCode(identifier, params).then(res => {
console.log('点击了初始化代码: ', res);
const {data} = res;
dispatch({
type: types.GET_WXCODE_BY_IDENTIFIER,
payload: data.content || ''
});
});
}
}
// 更新编辑器代码
export const updateWXCodeForEditor = (code) => {
return {
type: types.UPDATE_WXCODE_FOR_EDITOR,
payload: code
}
}
export const updateWxCode = (path, identifier, userCode, game_id, evaluate = 0,) => {
return fetchUpdateWxCode(identifier, {
path,
evaluate,
content: userCode,
game_id
});
}
// 定时更新代码内容
export const updateWXCodeForInterval = (identifier, path) => {
return (dispatch, getState) => {
const {wxCode, userCode, game_id, myIdentifier} = getState().wxcodeReducer;
if (wxCode !== userCode) {
updateWxCode(path, myIdentifier, userCode, game_id, 0);
}
}
}
// 评测
export const evaluateWxCode = (identifier, path) => {
return (dispatch, getState) => {
const {
userCode,
game_id,
myIdentifier,
exec_time,
path,
last_compile_output,
test_sets_count,
sets_error_count
} = getState().wxcodeReducer;
updateWxCode(path, myIdentifier, userCode, game_id, 1).then(res => {
// build
// const {} = res;
console.log(res);
const params = {
content_modified: 1,
sec_key: res.data.sec_key
}
console.log(params);
fetchWxCodeGameBuild(identifier, params).then(res => {
if (res.data.status === 1) {
// 定时调用 game_status fetchWxCodeGameStatus
let count = 1;
const intervalTime = 500;
function wxCodeGameStatus (intervalTime, finalTime, count, timer) {
const excuteTime = (count++) * intervalTime; // 当前执行时间
console.log(finalTime, count, excuteTime);
fetchWxCodeGameStatus(identifier).then(r => {
const { status, test_sets = [] } = r.data;
if (+status > -1 || ((excuteTime / 1000) > (finalTime + 1))) {
clearInterval(timer);
timer = null;
dispatch({
type: types.SHOW_WX_CODE_LOADING,
payload: false
});
setTimeout(() => {
// 显示测试集弹框
dispatch({
type: types.IS_SHOW_WXCODE_TEST_CASES,
payload: true
});
dispatch({
type: types.GET_WXCODE_TEST_CASE,
payload: {
test_sets,
game_id,
myIdentifier,
exec_time,
path,
last_compile_output,
test_sets_count,
sets_error_count
}
});
}, 50);
}
}).catch(err => {
dispatch({
type: types.SHOW_WX_CODE_LOADING,
payload: false
});
});
}
let timer = setInterval(() => {
wxCodeGameStatus(intervalTime, exec_time, count++, timer);
}, intervalTime);
}
})
}).catch(err => {
dispatch({
type: types.SHOW_WX_CODE_LOADING,
payload: false
});
});
}
}
// 显示测试集
export const showWXCodeTextCase = (flag) => {
return {
type: types.IS_SHOW_WXCODE_TEST_CASES,
payload: flag
}
}
// 显示测评中的状态
export const changeWXCodeEvaluateLoading = (flag) => {
return {
type: types.SHOW_WX_CODE_LOADING,
payload: flag
}
}

@ -17,6 +17,7 @@ import jupyterReducer from './jupyterReducer';
import commentReducer from './commentReducer'; import commentReducer from './commentReducer';
import tpiReducer from './tpiReducer'; import tpiReducer from './tpiReducer';
import staticReducer from './staticReducer'; import staticReducer from './staticReducer';
import wxcodeReducer from './wxcodeReducer';
export default combineReducers({ export default combineReducers({
testReducer, testReducer,
@ -28,5 +29,6 @@ export default combineReducers({
jupyterReducer, jupyterReducer,
commentReducer, commentReducer,
tpiReducer, tpiReducer,
staticReducer staticReducer,
wxcodeReducer
}); });

@ -0,0 +1,68 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2020-01-15 15:37:44
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-16 15:22:37
*/
import types from "../actions/actionTypes";
const initialState = {
wxCode: '',
userCode: '',
testCase: [],
game_id: '',
myIdentifier: '',
exec_time: 0,
last_compile_output: '',
test_sets_count: 0,
sets_error_count: 0,
path: '',
isShow: false,
showLoading: false
};
const wxcodeReducer = (state = initialState, action) => {
const { payload, type } = action;
switch (type) {
case types.GET_WXCODE_BY_IDENTIFIER:
return {
...state,
wxCode: payload,
userCode: payload
}
case types.GET_WXCODE_TEST_CASE:
return {
...state,
testCase: payload.test_sets,
game_id: payload.game_id,
myIdentifier: payload.myIdentifier,
exec_time: payload.exec_time,
path: payload.path,
last_compile_output: payload.last_compile_output,
test_sets_count: payload.test_sets_count,
sets_error_count: payload.sets_error_count
}
case types.UPDATE_WXCODE_FOR_EDITOR:
return {
...state,
userCode: payload
}
case types.IS_SHOW_WXCODE_TEST_CASES:
return {
...state,
isShow: payload
}
case types.SHOW_WX_CODE_LOADING:
return {
...state,
showLoading: payload
}
default:
return {
...state
}
}
}
export default wxcodeReducer;

@ -0,0 +1,44 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2020-01-15 15:44:36
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-16 11:20:24
*/
import axios from 'axios';
// 获取代码块
export async function fetchWxCode (identifier, params) {
const url = `/tasks/${identifier}/rep_content.json`;
return axios.get(url, {params});
}
// 获取测试值
export async function fetchWxCodeTextCase (identifier) {
const url = `/tasks/${identifier}.json`;
return axios.get(url);
}
// 更新代码块内容
export async function fetchUpdateWxCode (identifier, params) {
// /myshixuns/8etu3pilsa/update_file.json
const url = `/myshixuns/${identifier}/update_file.json`;
return axios.post(url, params);
}
// 恢复初始化
export async function fetchRestoreWxCode (identifier, params) {
const url = `/tasks/${identifier}/reset_original_code.json`;
return axios.get(url, {params});
}
// 评测
export async function fetchWxCodeGameBuild (identifier, params) {
const url = `/tasks/${identifier}/game_build.json`;
return axios.get(url, {params});
}
export async function fetchWxCodeGameStatus (identifier) {
const url = `/tasks/${identifier}/game_status.json`;
return axios.get(url);
}
Loading…
Cancel
Save