dev_cxt
jingquan huang 5 years ago
commit 7ec0efbcc7

@ -1,9 +1,35 @@
require 'base64'
class JupytersController < ApplicationController
include JupyterService
before_action :shixun, only: [:open, :open1, :test, :save]
def import_with_tpm
shixun = Shixun.find_by(identifier: params[:identifier])
upload_file = params["file"] || params["#{params[:file_param_name]}"] # 这里的file_param_name是为了方便其他插件名称
uid_logger("#########################file_params####{params["#{params[:file]}"]}")
raise "未上传文件" unless upload_file
content = Base64.encode64(upload_file.tempfile.read)
# upload to server
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/update"
tpiID = "tpm#{shixun.id}"
params = {tpiID: tpiID, content: content}
logger.info "test_juypter: uri->#{uri}, params->#{params}"
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级100")
end
render json: {status: 0}
end
def save_with_tpi
myshixun = Myshixun.find_by(identifier: params[:identifier])
jupyter_save_with_game(myshixun, params[:jupyter_port])
@ -42,5 +68,16 @@ class JupytersController < ApplicationController
render json: {status: 0, url: info[:url], port: info[:port]}
end
def active_with_tpm
shixun = Shixun.find_by(identifier: params[:identifier])
jupyter_active_tpm(shixun)
render json: {status: 0}
end
def active_with_tpi
myshixun = Myshixun.find_by(identifier: params[:identifier])
jupyter_active_tpm(myshixun)
render json: {status: 0}
end
end

@ -763,7 +763,7 @@ class ShixunsController < ApplicationController
else
commit = GitService.commits(repo_path: @repo_path).try(:first)
uid_logger("First comit########{commit}")
tip_exception("开启挑战前请先在Jupyter中填写内容") if commit.blank?
tip_exception("开启挑战前请先在Jupyter中填写内容并保存") if commit.blank?
commit_id = commit["id"]
cloud_bridge = edu_setting('cloud_bridge')
myshixun_identifier = generate_identifier Myshixun, 10

@ -191,5 +191,26 @@ module JupyterService
edu_setting('jupyter_service').gsub("PORT", jupyter_port)
end
def _jupyter_active(tpiID)
shixun_tomcat = edu_setting('cloud_bridge')
uri = "#{shixun_tomcat}/bridge/jupyter/active"
params = {:tpiID => tpiID}
res = uri_post uri, params
if res && res['code'].to_i != 0
raise("实训云平台繁忙繁忙等级120")
end
end
# tpm 延时
def jupyter_active_tpm(shixun)
tpiID = "tpm#{shixun.id}"
_jupyter_active(tpiID)
end
# tpi 延时
def jupyter_active_tpi(myshixun)
tpiID = myshixun.id
_jupyter_active(tpiID)
end
end

@ -41,7 +41,9 @@ json.top do
json.old_url @old_domain
# 云上实验室管理权限
json.laboratory_user current_laboratory.laboratory_users.exists?(user_id: @user&.id) || @user&.admin_or_business?
laboratory_user = current_laboratory.laboratory_users.exists?(user_id: @user&.id) || @user&.admin_or_business?
json.laboratory_user laboratory_user
json.laboratory_admin_url laboratory_user ? "/cooperative" : nil
end
json.down do

@ -33,6 +33,10 @@ Rails.application.routes.draw do
get :get_info_with_tpm
get :reset_with_tpi
get :reset_with_tpm
get :active_with_tpm
get :active_with_tpi
post :import_with_tpm
end
end

@ -0,0 +1,10 @@
class ModifyDescriptionForHacks < ActiveRecord::Migration[5.2]
def change
change_column :hacks, :description, :longtext
change_column :hack_codes, :code, :longtext
change_column :hack_user_lastest_codes, :code, :longtext
change_column :hack_user_codes, :code, :longtext
change_column :hack_user_debugs, :code, :longtext
end
end

@ -0,0 +1,51 @@
//用于嵌入到jupyter pod中的js
//guange 2019.12.18
var timebool=false;
window.onload=function(){
console.log("开始发送消息了");
timebool=true;
// runEvery10Sec();
}
function runEvery10Sec() {
// 1000 * 10 = 10 秒钟
// console.log("每隔10秒中一次");
require(["base/js/namespace"],function(Jupyter) {
Jupyter.notebook.save_checkpoint();
});
window.parent.postMessage('jupytermessage','*');
// if(timebool===true){
// setTimeout( runEvery10Sec, 1000 * 10 );
// }
}
window.onload=function(){
document.addEventListener('keydown', (e) => {
if (e.keyCode == 83 && (navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey)){
e.preventDefault();
console.log("ctrl+s");
window.parent.postMessage('jupytermessage','*');
}
});
window.addEventListener('message', (e) => {
if(e){
if(e.data){
if(e.data==="stopParent"){
//重置停止
timebool=false;
console.log("父窗口调用停止");
}else if(e.data==="clonsParent"){
console.log("父窗口调用启动");
//取消启动
timebool=true;
// runEvery10Sec();
}
}
}
});
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,35 @@
/*
* @Author: your name
* @Date: 2019-12-20 11:40:56
* @LastEditTime : 2019-12-20 13:38:49
* @LastEditors : Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: /notebook/Users/yangshuming/Desktop/new__educode/educoder/public/react/public/js/jupyter.js
*/
window.onload=function(){
require(["base/js/namespace"],function(Jupyter) {
Jupyter.notebook.save_checkpoint();
});
}
// //子目标父窗口接收子窗口发送的消息
// let message = {type: 'open', link:'需要发送的消息'};
//子窗口向父窗口发送消息,消息中包含我们想跳转的链接
window.parent.postMessage('jupytermessage','需要发送的消息');
// //目标父窗口接收子窗口发送的消息
// window.addEventListener('message', (e)=>{
// let origin = event.origin || event.originalEvent.origin;
// if (origin !== '需要发送的消息') {
// return;
// }else {
// //更换iframe的src,实现iframe页面跳转
// 执行方法
// }
// },false);

@ -52,7 +52,7 @@ export function initAxiosInterceptors(props) {
//proxy="http://47.96.87.25:48080"
proxy="https://pre-newweb.educoder.net"
proxy="https://test-newweb.educoder.net"
//proxy="https://test-jupyterweb.educoder.net"
proxy="https://test-jupyterweb.educoder.net"
//proxy="http://192.168.2.63:3001"
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求

@ -75,6 +75,10 @@ export function getUploadActionUrltwo(id) {
return `${getUrlmys()}/api/shixuns/${id}/upload_data_sets.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
export function getUploadActionUrlthree() {
return `${getUrlmys()}/api/jupyters/import_with_tpm.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}
export function getUploadActionUrlOfAuth(id) {
return `${getUrl()}/api/users/accounts/${id}/auth_attachment.json${isDev ? `?debug=${window._debugType || 'admin'}` : ''}`
}

@ -3,7 +3,7 @@
// export { default as OrderStateUtil } from '../routes/Order/components/OrderStateUtil';
export { getImageUrl as getImageUrl, getUrl as getUrl, getUrlmys as getUrlmys, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
, getUploadActionUrl as getUploadActionUrl,getUploadActionUrltwo as getUploadActionUrltwo , getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
, getUploadActionUrl as getUploadActionUrl,getUploadActionUrltwo as getUploadActionUrltwo ,getUploadActionUrlthree as getUploadActionUrlthree, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
, getTaskUrlById as getTaskUrlById, TEST_HOST ,htmlEncode as htmlEncode } from './UrlTool';
export { default as queryString } from './UrlTool2';

@ -4,8 +4,9 @@
* @Github:
* @Date: 2019-12-18 08:49:30
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 16:58:50
* @LastEditTime: 2019-12-20 16:07:37
*/
import './index.scss';
import 'quill/dist/quill.core.css'; // 核心样式
import 'quill/dist/quill.snow.css'; // 有工具栏
import 'quill/dist/quill.bubble.css'; // 无工具栏
@ -70,6 +71,8 @@ function QuillForEditor ({
useEffect(() => {
const quillNode = document.createElement('div');
editorRef.current.appendChild(quillNode);
const _quill = new Quill(editorRef.current, quillOption);
setQuill(_quill);
@ -157,9 +160,9 @@ function QuillForEditor ({
// 返回结果
return (
<div className='quill_editor_for_react_area' style={wrapStyle}>
<div ref={editorRef} style={style}></div>
</div>
<div className='quill_editor_for_react_area' style={wrapStyle}>
<div ref={editorRef} style={style}></div>
</div>
);
}

@ -0,0 +1,5 @@
.quill_editor_for_react_area{
.ql-editing{
left: 0 !important;
}
}

@ -1748,10 +1748,11 @@ class Listofworksstudentone extends Component {
} catch (e) {
}
// console.log("table1handleChange");
// console.log(sorter.columnKey);
try {
//学生成绩排序
if (sorter.columnKey === "finalscore") {
if (sorter.columnKey === "work_score") {
if (sorter.order === "ascend") {
//升序
this.setState({

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 16:02:36
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 19:47:32
* @LastEditTime: 2019-12-20 14:37:39
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
@ -24,7 +24,7 @@ const ControlSetting = (props) => {
identifier,
excuteState,
showOrHideControl,
commitRecordDetail,
commitTestRecordDetail,
changeLoadingState,
changeSubmitLoadingStatus,
changeShowOrHideControl,
@ -102,7 +102,7 @@ const ControlSetting = (props) => {
<TabPane tab={'代码执行结果'} key={'2'} style={{ height: '280px', overflowY: 'auto' }}>
<ExecResult
excuteState={excuteState}
excuteDetail={commitRecordDetail}
excuteDetail={commitTestRecordDetail}
/>
</TabPane>
</Tabs>
@ -137,14 +137,14 @@ const ControlSetting = (props) => {
const mapStateToProps = (state) => {
const {commonReducer, ojForUserReducer} = state;
const {loading, excuteState, submitLoading, showOrHideControl } = commonReducer;
const { commitRecordDetail } = ojForUserReducer;
const { commitTestRecordDetail } = ojForUserReducer;
return {
loading,
submitLoading,
excuteState,
showOrHideControl,
// identifier: user_program_identifier,
commitRecordDetail // 提交详情
commitTestRecordDetail // 提交详情
};
};
// changeSubmitLoadingStatus

@ -51,7 +51,7 @@
align-items: center;
z-index: 20;
height: 56px;
padding-right: 30px;
padding-right: 20px;
padding-left: 10px;
background: rgba(18,28,36,1);
// background:rgba(48,48,48,1);

@ -4,11 +4,11 @@
* @Github:
* @Date: 2019-11-27 15:02:52
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 19:36:24
* @LastEditTime: 2019-12-20 20:07:11
*/
import './index.scss';
import React, { useState, useRef, useEffect } from 'react';
import { Drawer, Modal, Icon, Badge } from 'antd';
import { Drawer, Tooltip, Badge } from 'antd';
import { fromStore, CNotificationHOC } from 'educoder';
import { connect } from 'react-redux';
import MonacoEditor from '@monaco-editor/react';
@ -137,17 +137,45 @@ function MyMonacoEditor (props, ref) {
<div className="code_title">
{/* 未保存时 ? '学员初始代码文件' : main.x */}
<span className='flex_strict' style={{ color: '#ddd'}}>{identifier ? language ? maps[language.toLowerCase()] : '' : '学员初始代码文件'}</span>
<span className={_classnames}>{identifier ? '已保存' : ''}</span>
<Badge
className="flex_normal"
style={{ color: '#666'}}
dot={notice}
onClick={handleUpdateNotice}
<span className={_classnames}>{hadCodeUpdate ? '已保存' : ''}</span>
{/* <Tooltip
style={{ background: 'gold' }}
className="tooltip_style"
title="通知"
placement="bottom"
> */}
<Tooltip
placement="bottom"
title="通知"
>
<Icon type="bell" />
</Badge>
<span onClick={handleRestoreCode} className="flex_normal" style={{ display: identifier ? 'inline-block' : 'none'}}>{renderRestore}</span>
<MyIcon className='code-icon' type="iconshezhi" onClick={handleShowDrawer}/>
<Badge
className="flex_normal"
style={{ color: '#666'}}
dot={notice}
onClick={handleUpdateNotice}
>
{/* <Icon type="bell" /> */}
<MyIcon type="iconxiaoxi1" />
</Badge>
</Tooltip>
<Tooltip
placement="bottom"
title="恢复"
>
<MyIcon
className="flex_normal"
onClick={handleRestoreCode}
type="iconzaicizairu"
style={{ display: identifier ? 'inline-block' : 'none'}}
/>
{/* <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}/>
</Tooltip>
</div>
<MonacoEditor
height={height}

@ -7,7 +7,7 @@
background-color: rgba(18,28,36,1);
color: #fff;
height: 56px;
padding: 0 30px;
padding: 0 20px;
.flex_strict{
flex: 1;
}
@ -15,7 +15,7 @@
.flex_normal{
color: #E51C24;
cursor: pointer;
margin-right: 30px;
margin-right: 20px;
}
.code-icon{
cursor: pointer;

@ -3,6 +3,7 @@
position: absolute;
color: #fff;
line-height: 65px;
left: 20px;
// height: 65px;
.student_img,
.student_nicker{

@ -70,13 +70,13 @@ const NewOrEditTask = (props) => {
// 模拟挑战
const imitationChallenge = () => {
// 调用 start 接口, 成功后跳转到模拟页面
startProgramQuestion(identifier, props);
identifier && startProgramQuestion(identifier, props);
}
// 开始挑战
const startChallenge = () => {
// 调用 start 接口, 成功后跳转到开启实战
// TODO
startProgramQuestion(identifier, props);
identifier && startProgramQuestion(identifier, props);
}
// 取消

@ -4,11 +4,11 @@
* @Github:
* @Date: 2019-11-21 09:19:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 17:54:28
* @LastEditTime: 2019-12-20 17:32:10
*/
import './index.scss';
import React, { useState } from 'react';
import { Collapse, Icon, Input, Form, Button } from 'antd';
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';
@ -18,14 +18,14 @@ const FormItem = Form.Item;
const AddTestDemo = (props) => {
const {
key,
onSubmitTest,
// onSubmitTest,
onDeleteTest,
testCase,
testCaseValidate,
isOpen
} = props;
const [isEditor, setIsEditor] = useState(false); // 是否是编辑
// const [isEditor, setIsEditor] = useState(false); // 是否是编辑
// 删除操作
const handleDeletePanel = (e) => {
@ -68,59 +68,60 @@ const AddTestDemo = (props) => {
const genExtra = () => (
<Icon
type="close"
className="collapse_close_icon"
onClick={handleDeletePanel}
/>
)
// 取消操作
const handleReset = (e) => {
e.preventDefault();
props.form.resetFields();
}
// 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 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 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>
);
}
}
}
// 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>
// );
// }
// }
// }
/**
* 文本输入框可编辑的情况
@ -128,9 +129,9 @@ const AddTestDemo = (props) => {
* 2. isAdd false isEditor 为true
* @param {*} testCase
*/
const isDisabled = (testCase) => {
return !testCase.isAdd && !isEditor;
};
// const isDisabled = (testCase) => {
// return !testCase.isAdd && !isEditor;
// };
// const {input = {}, output = {}} = (testCasesValidate[index] = {});
const activePane = {
@ -158,7 +159,8 @@ const AddTestDemo = (props) => {
rows={5}
value={testCase.input}
onChange={handleInputChange}
disabled={isDisabled(testCase)}/>
// disabled={isDisabled(testCase)}
/>
</FormItem>
<FormItem
label={<span className={'label_text'}>输出</span>}
@ -170,9 +172,10 @@ const AddTestDemo = (props) => {
rows={5}
value={testCase.output}
onChange={handleOutputChange}
disabled={isDisabled(testCase)}/>
// disabled={isDisabled(testCase)}
/>
</FormItem>
{renderSubmitBtn()}
{/* {renderSubmitBtn()} */}
</Form>
</Panel>
</Collapse>

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-20 10:35:40
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 17:23:10
* @LastEditTime: 2019-12-20 16:53:55
*/
import './index.scss';
// import 'katex/dist/katex.css';
@ -24,17 +24,20 @@ 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'}
],
@ -310,8 +313,8 @@ class EditTab extends React.Component {
>
<div style={{ marginTop: '15px'}}>
<QuillForEditor
style={{ height: '200px', 'overflowY': 'auto' }}
placeholder="init content"
style={{ height: '200px' }}
placeholder="请输入描述信息"
onContentChange={handleContentChange}
options={quillConfig}
value={ojForm.description}

@ -48,7 +48,7 @@
.test_demo_title,
.test_demo_ctx,
.editor_form{
margin: 0 30px;
margin: 0 20px;
.ant-form-explain{
margin-top: 5px;
@ -66,9 +66,9 @@
&.fix_top{
position: absolute;
top: 43px;
left: -30px;
right: -30px;
padding: 0 30px;
left: -20px;
right: -20px;
padding: 0 26px 0 20px;
// background: gold;
background: rgb(249,249,249);
z-index: 1000;
@ -77,5 +77,27 @@
.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;
// }
}

@ -17,7 +17,7 @@
}
.code-title,
.pane_control_opts{
padding: 0 30px;
padding: 0 20px;
}
.code-title{

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-12-04 08:36:21
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-10 18:55:02
* @LastEditTime: 2019-12-20 20:05:57
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
@ -37,7 +37,6 @@ function RecordDetail (props) {
useEffect(() => {
// 根据id获取记录详情
console.log('提交记录详情', recordDetail);
getUserCommitRecordDetail(id, 'detail');
}, []);
@ -68,7 +67,7 @@ function RecordDetail (props) {
<span>{detail.name || 'test'}</span>
</div>
<div className={'study_quit'}>
<Button>
<Button style={{ visibility: identifier ? 'visible' : 'hidden'}}>
<Link to={`/myproblems/${identifier}`}>返回该题</Link>
</Button>
</div>
@ -79,7 +78,7 @@ function RecordDetail (props) {
</div>
<div className="detail_ctx_status">
<span className="status_label">
状态: <span className="status_label_error">{reviewResult[detail.status]}</span>
状态: <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">
@ -87,7 +86,10 @@ function RecordDetail (props) {
</span>
</span>
<span className="status_label">
语言: <span className="status_label_sub">C</span>
语言: <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 && (+detail.execute_time * 1000)}ms`}</span>
</span>
</div>
<div className="result_error_area">
@ -96,6 +98,7 @@ function RecordDetail (props) {
<div className="detail_ctx_header">
<h2 className="header_h2">提交内容</h2>
<Button
style={{ visibility: identifier ? 'visible' : 'hidden'}}
className={'header_btn'}
type="primary"
>

@ -2,7 +2,7 @@
.record_detail_area{
.record_detail_ctx{
padding: 0 30px;
padding: 0 20px;
.detail_ctx_header{
position: relative;
height: 56px;

@ -10,7 +10,7 @@
// background:rgba(34,34,34,1);
// background: #1E1E1E;
background: rgba(7,15,25,1);
padding:0 30px;
padding:0 20px;
}
.task_header{
@ -88,7 +88,7 @@
.add_editor_list_area{
background: #fff;
padding: 0 30px;
padding: 0 20px;
margin: 0;
.add_editor_item{
display: inline-block;

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-23 10:53:19
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 19:48:20
* @LastEditTime: 2019-12-20 14:54:39
*/
import './index.scss';
import React, { useEffect, useState } from 'react';
@ -92,9 +92,12 @@ function StudentStudy (props) {
changeShowOrHideControl(false);
props.saveEditorCodeForDetail();
props.history.push(`/problems/${_hack_id}/edit`);
props.clearOjForUserReducer();
}
// 处理退出
const handleClickQuit = () => {
// 退出时,清空内容
props.clearOjForUserReducer();
// 将控制台关闭
changeShowOrHideControl(false);
props.saveEditorCodeForDetail();
@ -116,7 +119,11 @@ function StudentStudy (props) {
</div>
<div className={'study_quit'}>
{/* to={`/problems/${_hack_id}/edit`} */}
<span onClick={handleClickEditor} className={`quit-btn`}>
<span
style={{ display: userInfo.hack_manager ? 'inline-block' : 'none' }}
onClick={handleClickEditor}
className={`quit-btn`}
>
<Icon type="form" className="quit-icon"/> 编辑
</span>
{/* to="/problems" */}
@ -168,7 +175,8 @@ const mapDispatchToProps = (dispatch) => ({
saveEditorCodeForDetail: (code) => dispatch(actions.saveEditorCodeForDetail(code)),
// 恢复初始代码
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
changeShowOrHideControl: (flag) => dispatch(actions.changeShowOrHideControl(flag))
changeShowOrHideControl: (flag) => dispatch(actions.changeShowOrHideControl(flag)),
clearOjForUserReducer: () => dispatch(actions.clearOjForUserReducer())
});
export default withRouter(connect(

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 09:49:33
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-05 10:38:49
* @LastEditTime: 2019-12-20 19:35:01
*/
import './index.scss';
import React, { useState, useEffect } from 'react';
@ -73,12 +73,16 @@ const CommitRecord = (props) => {
commitRecord,
// excuteState,
language,
operateType,
commitRecordDetail,
getUserCommitRecord
} = props;
const [pagination, setPagination] = useState(paginationConfig);
const [tableData, setTableData] = useState([]);
let clipboard;
// const [recordDetail, setRecordDetail] = useState({});
const [renderCtx, setRenderCtx] = useState(() => {
return function () {
@ -89,14 +93,14 @@ const CommitRecord = (props) => {
const renderRecordDetail = () => {
const {
id,
error_line,
error_msg,
execute_memory,
execute_time,
input,
output,
// error_line,
// error_msg,
// execute_memory,
// execute_time,
// input,
// output,
status,
expected_output
// expected_output
} = commitRecordDetail;
if (Object.keys(commitRecordDetail).length > 0) {
console.log('当前状态====》》》', status);
@ -109,7 +113,9 @@ const CommitRecord = (props) => {
<p className={'record_result'}>
执行结果: <span className={classes}>{reviewResult[status]}</span>
</p>
<p className={showErrorCopy} data-clipboard-target="#errcode">
<p
onClick={clickCopyErrInfo}
className={showErrorCopy} data-clipboard-target="#errcode">
<span>
复制错误信息 <Icon type="copy" className={'icon_style'}/>
</span>
@ -143,19 +149,35 @@ const CommitRecord = (props) => {
// 提交详情变化时,显示当前提交信息
useEffect(() => {
// setRecordDetail(commitRecordDetail);
setRenderCtx(() => (renderRecordDetail))
}, [commitRecordDetail]);
if (operateType === 'submit') {
setRenderCtx(() => (renderRecordDetail))
}
}, [commitRecordDetail, operateType]);
// 复制功能
let count = 0;
useEffect(() => {
if (commitRecordDetail.status !== 0) {
const clipboard = new ClipboardJS('.copy_error');
clipboard.on('success', (e) => {
message.success('复制成功');
e.clearSelection();
});
}
}, [commitRecordDetail.status]);
clipboard = new ClipboardJS('.copy_error');
clipboard && clipboard.on('success', (e) => {
e.clearSelection();
if (count > 0) return;
count++;
message.success('复制成功');
setTimeout(() => {
message.destroy();
}, 300);
});
}, []);
const clickCopyErrInfo = () => {
count = 0;
}
// if (commitRecordDetail.status !== 0) {
// clipboard.on('success', (e) => {
// console.log('成功=====》》》》》');
// message.success('复制成功');
// e.clearSelection();
// });
// }
//
const handleTableChange = (pagination) => {
setPagination(Object.assign({}, pagination));
@ -184,7 +206,8 @@ const mapStateToProps = (state) => {
user_program_identifier,
commitRecordDetail,
commitRecord,
hack
hack,
operateType
} = ojForUserReducer;
const { excuteState } = commonReducer;
return {
@ -192,7 +215,8 @@ const mapStateToProps = (state) => {
commitRecordDetail,
commitRecord, // 提交记录
excuteState, // 代码执行状态
language: hack.language
language: hack.language,
operateType
}
}
const mapDispatchToProps = (dispatch) => ({

@ -1,6 +1,8 @@
.commit_record_area{
// padding: 20px 30px;
padding: 0 30px;
padding: 0 20px;
overflow-y: auto;
height: calc(100vh - 177px);
.record_header{
display: flex;
// justify-content: space-between;

@ -16,7 +16,7 @@
box-sizing: border-box;
border-top: 1px solid rgba(244,244,244,1);
// background: pink;
padding: 0 30px;
padding: 0 20px;
// background-color: rgba(250,250,250,1);
background: #fff;
@ -32,7 +32,7 @@
}
.commit_record_area{
padding: 0 30px;
padding: 0 20px;
// height: calc(100vh - 178px);
// overflow-y: auto;
}
@ -40,6 +40,7 @@
.task_desc_area{
height: calc(100vh - 242px);
overflow-y: auto;
padding: 0 0 0 15px;
}
.desc_area_header{
@ -47,7 +48,7 @@
justify-content: space-between;
align-items: center;
height: 64px;
padding: 0 30px;
padding: 0 20px;
.header_flex{
font-size: 14px;
.flex_label{
@ -64,7 +65,7 @@
.student_study_header{
.study_quit{
position: absolute;
right: 30px;
right: 20px;
}
.quit-btn{
cursor: pointer;

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 09:49:30
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 09:22:52
* @LastEditTime: 2019-12-20 09:39:35
*/
import '../index.scss';
import React from 'react';

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 14:59:51
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 19:13:05
* @LastEditTime: 2019-12-20 14:01:57
*/
import React, { useState, useEffect } from 'react';
import {connect} from 'react-redux';
@ -27,6 +27,7 @@ const RightPane = (props) => {
updateNotice,
saveUserInputCode,
restoreInitialCode,
saveOpacityType,
saveUserCodeForInterval
} = props;
@ -43,6 +44,7 @@ const RightPane = (props) => {
const handleSubmitForm = () => {
// 提交时, 先调用提交接口,提交成功后,循环调用测评接口
// saveOpacityType('submit');
submitUserCode(identifier, submitInput, 'submit');
// // 提交时,先调用评测接口, 评测通过后才调用保存接口
// updateCode(identifier, submitInput, 'submit');
@ -71,6 +73,7 @@ const RightPane = (props) => {
// 代码调试
const handleDebuggerCode = (value) => {
// 调用保存代码块接口,成功后,调用调试接口
// saveOpacityType('debug');
updateCode(identifier, value, 'debug');
}
// 恢复初始代码
@ -137,6 +140,7 @@ const mapDispatchToProps = (dispatch) => ({
saveUserCodeForInterval: (identifier, code) => dispatch(actions.saveUserCodeForInterval(identifier, code)),
// 恢复初始代码
restoreInitialCode: (identifier, msg) => dispatch(actions.restoreInitialCode(identifier, msg)),
// saveOpacityType: (type) => dispatch(actions.saveOpacityType(type))
});
export default connect(

@ -15,3 +15,28 @@
left: 13px;
user-select: none;
}
.jupyter_float_button {
background-image: url(./images/float_switch.jpg);
height: 112px;
width: 38px;
position: absolute;
right: 0px;
top: 32%;
cursor: pointer;
left:auto;
z-index: 99999999;
}
.jupyter_float_button .text {
position: relative;
writing-mode: vertical-rl;
top: 36px;
color: #fff;
left: 13px;
user-select: none;
}
.newjupyter_float_button{
right: 330px;
}

@ -1,5 +1,5 @@
import React, { Component } from 'react';
import '../VNC.css'
const $ = window.$;
class FloatButton extends Component {
componentDidMount() {

@ -109,30 +109,48 @@ class TPMBanner extends Component {
if (prevProps != this.props) {
let shixunopenprocess=window.localStorage.shixunopenprocess;
let openopenpublictype=window.localStorage.openopenpublictype;
if(this.props.shixunsDetails&&this.props.shixunsDetails.shixun_status === 0 && this.props.identity < 5){
if(shixunopenprocess===undefined||shixunopenprocess===false){
this.setState({
openknow:true
})
}else{
this.setState({
openknow:false
})
if(this.props.status===0&&this.props.openknows===false){
if(this.props.shixunsDetails&&this.props.shixunsDetails.shixun_status === 0 && this.props.identity < 5){
if(shixunopenprocess===undefined||shixunopenprocess===false){
this.setState({
openknow:true
})
}else{
this.setState({
openknow:false
})
}
}
}else{
this.setState({
openknow:false
})
}
if(this.props.shixunsDetails&&this.props.shixunsDetails.shixun_status === 2 && this.props.shixunsDetails&&this.props.shixunsDetails.public===0 && this.props.identity < 5){
if(openopenpublictype===undefined||openopenpublictype===false){
this.setState({
openshowpublictype:true
})
}else{
this.setState({
openshowpublictype:false
})
if(this.props.public===0&&this.props.status>1&&this.props.openknows===false){
if(this.props.shixunsDetails&&this.props.shixunsDetails.shixun_status === 2 && this.props.shixunsDetails&&this.props.shixunsDetails.public===0 && this.props.identity < 5){
if(openopenpublictype===undefined||openopenpublictype===false){
this.setState({
openshowpublictype:true
})
}else{
this.setState({
openshowpublictype:false
})
}
}
}else{
this.setState({
openshowpublictype:false
})
}
}
}
@ -779,8 +797,8 @@ class TPMBanner extends Component {
};
//
// console.log(this.props.shixunsDetails&&this.props.shixunsDetails.is_jupyter)
console.log(this.props)
console.log(this.state)
// console.log(this.props)
// console.log(this.state)
return (
shixunsDetails === undefined ? "" :
@ -850,9 +868,7 @@ class TPMBanner extends Component {
</li>
</ul>
{
this.props.is_jupyter===true?"":
{ this.props.is_jupyter===true?"":
<Popover placement="right" content={
<div style={{"width": "530px"}} >
<div className="pr">
@ -914,9 +930,7 @@ class TPMBanner extends Component {
<MyRate allowHalf defaultValue={star_info[0]} disabled/>
</div>
</div>
</Popover>
}
</Popover>}
{
@ -1000,8 +1014,8 @@ class TPMBanner extends Component {
<Popover
content={
<pre className={"bannerpd201"}>
<div>您编辑完成后可以马上使用到自</div>
<div className={"wechatcenter mt10"}>己的课堂和实训课程</div>
<div>点击发布后可以马上应用到自</div>
<div className={"wechatcenter mt10"}>己的课堂和课程</div>
<div className={"wechatcenter mt15"}><Button type="primary" onClick={this.openknow} >我知道了</Button></div>
</pre>
}

@ -461,7 +461,7 @@ class TPMDataset extends Component {
const uploadProps = {
width: 600,
fileList,
multiple: false,
multiple: true,
//multiple 是否支持多选 查重的时候不能多选 不然弹许多框出来
// https://github.com/ant-design/ant-design/issues/15505
// showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。

@ -298,7 +298,7 @@ export default class Shixuninformation extends Component {
return (
<div>
<div className="educontent mb200 edu-back-white padding10-20 pdb30 mb50">
<div className="clearfix ml40">
{this.props&&this.props.is_jupyter===true?"":<div className="clearfix ml40">
<span className="color-grey-6 mt5 fl font-16 ml20" style={{minWidth: '45px'}}>复制:</span>
<span className="fl mt8 ml13">
<Checkbox
@ -306,7 +306,7 @@ export default class Shixuninformation extends Component {
onChange={this.CheckboxonChange}></Checkbox>
<label style={{top: '6px'}} className="color-grey-9 ml10">选中则允许已职业认证的教师复制该实训</label>
</span>
</div>
</div>}
<div className="edu-back-white mb10 ml30 mt20">
<div>

@ -50,10 +50,15 @@ class Shixuninformation extends Component {
}
componentDidMount() {
let anchorElement = document.getElementById("newcourseContentMD");
if(anchorElement){
this.scrollToAnchor("newcourseContentMD");
let query=this.props.location.search
const types = query.split('?edit=')
if(types[1]==="1"){
let anchorElement = document.getElementById("newcourseContentMD");
if(anchorElement){
this.scrollToAnchor("newcourseContentMD");
}
}
}
componentDidUpdate(prevProps, prevState) {

@ -9,14 +9,34 @@
import './index.scss';
import React, { useEffect, useState } from 'react';
import SplitPane from 'react-split-pane';
import { Button, Modal } from 'antd';
import { Button, Modal,Drawer ,Pagination,Empty,Tooltip,Icon,message,Statistic,Spin} from 'antd';
import {
connect
} from 'react-redux';
import FloatButton from '../../page/component/FloatButton';
import UserInfo from '../../developer/components/userInfo';
import actions from '../../../redux/actions';
import LeftPane from './leftPane';
import RightPane from './rightPane';
import MyIcon from "../../../common/components/MyIcon";
function jsCopy(s) {
var copyEle = document.getElementById(s);
const range = document.createRange(); // 创造range
window.getSelection().removeAllRanges(); //清除页面中已有的selection
range.selectNode(copyEle); // 选中需要复制的节点
window.getSelection().addRange(range); // 执行选中元素
const copyStatus = document.execCommand("Copy"); // 执行copy操作
// 对成功与否定进行提示
copyStatuss(copyStatus)
}
function copyStatuss(copyStatus){
if (copyStatus) {
message.success('复制成功');
} else {
message.error('复制失败');
}
}
function JupyterTPI (props) {
// 获取 identifier 值
@ -39,14 +59,63 @@ function JupyterTPI (props) {
changeLoadingState,
changeGetJupyterUrlState,
jupyter_identifier,
changeCurrentPage
changeCurrentPage,
changeshowDrawer,
drawervisible,
reset_with_tpi,
jupytertime,
active_with_tpi,
spinning,
updataspinning
} = props;
const emptyCtx = (
<div className="jupyter_empty">
<Empty />
</div>
);
const { Countdown } = Statistic;
const {identifier} = params;
const [userInfo, setUserInfo] = useState({});
const [jupyterInfo, setJupyterInfo] = useState({});
const [updateTip, setUpdateTip] = useState(true);
const [myIdentifier, setMyIdentifier] = useState('');
const [renderCtx, setRenderCtx] = useState(() => (emptyCtx));
// 保存代码
const addEventListeners = () => {
window.addEventListener('message', (e) => {
console.log("触发了jupytermessage");
if(e){
if(e.data){
if(e.data==="jupytermessage"){
saveJupyterTpi();
}
}
}
})
}
const stopposttpip=(sum)=>{
var _iframe = document.getElementById("rightPaneframe");
if(_iframe == null || _iframe == undefined || _iframe == ""){
return;
}
if(sum===1){
_iframe.contentWindow.postMessage("stopParent", "*");
}else{
_iframe.contentWindow.postMessage("clonsParent", "*");
}
}
useEffect(() => {
addEventListeners()
}, []);
useEffect(() => {
/* jupyter TPI
* 获取 用户信息,
@ -56,6 +125,7 @@ function JupyterTPI (props) {
}, [identifier]);
useEffect(() => {
// 设置jupyter信息
setJupyterInfo(jupyter_info || {});
const {user, tpm_modified, myshixun_identifier} = jupyter_info;
@ -69,10 +139,13 @@ function JupyterTPI (props) {
// 同步代码
if (tpm_modified && updateTip && myshixun_identifier) {
setUpdateTip(false);
updataspinning(true)
Modal.confirm({
title: '更新通知',
content: (<div className="update_notice">
{stopposttpip(1)}
<p className="update_txt">关卡任务的代码文件有更新啦</p>
<p className="update_txt">更新操作将保留已完成的评测记录和成绩</p>
<p className="update_txt">还未完成评测的任务代码请自行保存</p>
@ -81,13 +154,18 @@ function JupyterTPI (props) {
cancelText: '取消',
onOk () {
syncJupyterCode(myshixun_identifier, '同步成功');
}
},onCancel() {
updataspinning(false)
stopposttpip(2)
},
})
}
}, [props]);
// 重置实训
const handleClickResetTpi = () => {
stopposttpip(1)
Modal.confirm({
title: '重置实训',
content: (
@ -103,7 +181,39 @@ function JupyterTPI (props) {
if (myIdentifier) {
syncJupyterCode(myIdentifier, '重置成功');
}
}
},
onCancel() {
stopposttpip(2)
},
})
}
// 重置环境
const handleEnvironmentTpi = () => {
stopposttpip(1)
updataspinning(true)
Modal.confirm({
title: '重置环境',
content: (
<p style={{ lineHeight: '24px' }}>
你在本文件中修改的内容将丢失,<br />
是否确定重置环境
</p>
),
okText: '确定',
cancelText: '取消',
onOk () {
console.log('调用重置代码....', myIdentifier);
// if (myIdentifier) {
//
// }
reset_with_tpi(myIdentifier, '重置成功');
},
onCancel() {
updataspinning(false)
stopposttpip(2)
},
})
}
@ -138,22 +248,98 @@ function JupyterTPI (props) {
getJupyterTpiDataSet(jupyter_identifier);
}
const swtichFirstDrawer = () => {
changeshowDrawer(!drawervisible)
}
const firstDrawerWidth = ()=>{
return 260
};
// 分页处理
const handleChangePage = (page) => {
handlePageChange(page);
}
// const listCtx = ;
useEffect(() => {
if (dataSets.length > 0) {
console.log('数据集的个数: ', dataSets.length);
const oList = dataSets.map((item, i) => {
return (
<li className="jupyter_item" key={`key_${i}`}>
<Tooltip
placement="right"
// title={item.file_path}
mouseLeaveDelay={0.3}
>
<div className="sortinxdirection">
<Icon type="file-text" className="jupyter_icon fl lineheighttaj" />
<a className="jupyter_name ml10 maxnamewidth150 lineheighttaj colorlineheighttaj" title={item.title}>{item.title}</a>
<a className={"fr color-blue lineheighttaj"}
onClick={() => {
jsCopy("file_path"+i)
}}>复制地址</a>
</div>
<input id={"file_path"+i} className={"file_path_input"} value={item.file_path}/>
</Tooltip>
</li>
);
});
const oUl = (
<ul className="jupyter_data_list">
{ oList }
</ul>
);
setRenderCtx(oUl);
}
}, [props]);
const onFinish= () =>{
Modal.confirm({
title: '倒计时截止',
content: (
<p style={{ lineHeight: '24px' }}>
Jupyter将中断服务是否需要延长使用时间
</p>
),
okText: '确定',
cancelText: '取消',
onOk () {
active_with_tpi(myIdentifier, '重置成功');
}
})
}
return (
<Spin tip="加载中..." spinning={spinning}>
<div className="jupyter_area">
<div className="jupyter_header">
<UserInfo userInfo={userInfo} />
<p className="jupyter_title">
<span className="title_desc" style={{ marginTop: '20px' }}>{jupyterInfo.name}</span>
<span className="title_time"></span>
<span className="title_desc" style={{ marginTop: '10px' }}>{jupyterInfo.name}</span>
<span className="title_time jupytertitle_time">
<Countdown value={jupytertime} format="HH:mm:ss" onFinish={onFinish}/>
</span>
</p>
<p className="jupyter_btn">
{/* sync | poweroff */}
{/*<Button*/}
{/* className="btn_common"*/}
{/* type="link"*/}
{/* icon="history"*/}
{/* onClick={handleClickResetTpi}*/}
{/*>重置实训</Button>*/}
<Button
className="btn_common"
type="link"
icon="sync"
onClick={handleClickResetTpi}
>重置实训</Button>
onClick={handleEnvironmentTpi}
>重置环境</Button>
<Button
className="btn_common"
type="link"
@ -162,16 +348,17 @@ function JupyterTPI (props) {
>退出实训</Button>
</p>
</div>
<div className="jupyter_ctx">
<SplitPane split="vertical" minSize={350} maxSize={-350} defaultSize="30%">
<div className={'split-pane-left'}>
<LeftPane
dataSets={dataSets}
total={total}
pagination={pagination}
onPageChange={handlePageChange}
/>
</div>
<SplitPane split="vertical" minSize={350} maxSize={-350} defaultSize="100%">
{/*<div className={'split-pane-left'}>*/}
{/* <LeftPane*/}
{/* dataSets={dataSets}*/}
{/* total={total}*/}
{/* pagination={pagination}*/}
{/* onPageChange={handlePageChange}*/}
{/* />*/}
{/*</div>*/}
<SplitPane split="vertical" defaultSize="100%" allowResize={false}>
<RightPane
identifier={myIdentifier}
@ -181,11 +368,41 @@ function JupyterTPI (props) {
onReloadUrl={handleOnReloadUrl}
onSave={handleOnSave}
/>
<div />
<FloatButton onClick={swtichFirstDrawer} className={drawervisible===false?"jupyter_float_button":"jupyter_float_button newjupyter_float_button"}>{"数据集"}</FloatButton>
</SplitPane>
</SplitPane>
<Drawer
placement={"right"}
closable={false}
mask={false}
// onClose={this.onClose}
visible={drawervisible}
className={"RightPaneDrawer"}
>
{/*<p className={"RightPaneDrawertop"}></p>*/}
<div className="jupyter_data_sets_area newjupyter_data_sets_area">
<h2 className="jupyter_h2_title">
{/*<MyIcon type="iconwenti" className="jupyter_data_icon"/>*/}
<i className={"iconfont icon-base"}></i>
{/* <span className="iconfont icon-java jupyter_data_icon"></span>数据集 */}
</h2>
{ renderCtx }
<div className='jupyter_pagination'>
{total<20?"":<Pagination
simple
current={pagination.page}
pageSize={pagination.limit}
total={total}
onChange={handleChangePage}
/>}
</div>
</div>
</Drawer>
</div>
</div>
</Spin>
);
}
@ -199,7 +416,7 @@ const mapStateToProps = (state) => {
jupyter_pagination,
jupyter_identifier
} = state.jupyterReducer;
const { loading } = state.commonReducer;
const { loading ,drawervisible,jupytertime,spinning} = state.commonReducer;
return {
loading,
jupyter_info,
@ -208,7 +425,10 @@ const mapStateToProps = (state) => {
jupyter_tpi_url_state,
total: jupyter_data_set_count,
pagination: jupyter_pagination,
jupyter_identifier
jupyter_identifier,
drawervisible,
jupytertime,
spinning
};
}
@ -217,11 +437,20 @@ const mapDispatchToProps = (dispatch) => ({
getJupyterInfo: (identifier) => dispatch(actions.getJupyterInfo(identifier)),
// 重置代码
syncJupyterCode: (identifier, msg) => dispatch(actions.syncJupyterCode(identifier, msg)),
// 重置代码
reset_with_tpi: (identifier, msg) => dispatch(actions.reset_with_tpi(identifier, msg)),
getJupyterTpiDataSet: (identifier, current) => dispatch(actions.getJupyterTpiDataSet(identifier, current)),
getJupyterTpiUrl: (identifier) => dispatch(actions.getJupyterTpiUrl(identifier)),
saveJupyterTpi: () => dispatch(actions.saveJupyterTpi()),
changeLoadingState: (flag) => dispatch(actions.changeLoadingState(flag)),
changeCurrentPage: (current) => dispatch(actions.changeCurrentPage(current))
changeCurrentPage: (current) => dispatch(actions.changeCurrentPage(current)),
//展开Drawer
changeshowDrawer: (type) => dispatch(actions.changeshowDrawer(type)),
//倒计时增加
addjypertime: (type) => dispatch(actions.addjypertime(type)),
//延时
active_with_tpi:(identifier, msg) => dispatch(actions.active_with_tpi(identifier, msg)),
updataspinning:(identifier, msg) => dispatch(actions.updataspinning(identifier, msg)),
});
export default connect(

@ -56,6 +56,7 @@
line-height: 60px;
background-color: #070F1A;
padding-left: 30px;
z-index:999999;
.jupyter_title{
display: flex;
flex-direction: column;
@ -103,3 +104,93 @@
}
}
}
.RightPaneDrawer{
.RightPaneDrawertop{
width:330px;
height:29px;
background:rgba(17,28,36,1);
}
.ant-drawer-content-wrapper{
width:330px !important;
box-shadow: -2px 0 8px #070F1A !important;
}
.ant-drawer-body{
padding: 0px;
}
.ant-drawer-wrapper-body{
padding-top: 60px;
background: #070F1A;
padding-bottom: 40px;
}
.ant-pagination{
color:#fff !important;
}
}
.newjupyter_data_sets_area{
background:#070F1A !important;
.jupyter_h2_title {
padding-left: 20px;
height:49px;
line-height: 49px;
background: #070F1A !important;
border-bottom: 1px solid #17212F !important;
color:#FFFFFF !important;
border-top: 1px solid #17212F !important;
}
.iconfont{
color:#28b887!important;
font-size: 30px !important;
margin-right: 20px;
}
.jupyter_pagination{
border-top: 1px solid #070F1A !important;
padding-left: 50px;
}
.jupyter_name{
color:#FFFFFF !important;
}
.file_path_input{
position: absolute;
right: -50%;
}
}
.maxnamewidth150 {
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
cursor: default;
width: 150px;
}
.sortinxdirection{
display: flex;
flex-direction:row;
}
.lineheighttaj{
line-height: 50px !important;
}
.colorlineheighttaj{
color: #ffffff;
}
.jupytertitle_time{
.ant-statistic-content{
font-size: 20px !important;
}
.ant-statistic-content-value{
color:#fff !important;
font-size: 17px !important;
}
}
.jupyter_data_list{
padding-left: 20px;
}

@ -2,15 +2,15 @@
height: 100%;
background: #fff;
.jupyter_h2_title{
height: 44px;
line-height: 44px;
//height: 44px;
//line-height: 44px;
// background-color: #EEEEEE;
background: #fff;
padding: 0 30px;
padding: 0 20px;
font-size: 16px;
// box-size: border-box;
box-sizing: border-box;
border-bottom: 1px solid rgba(238,238,238,1);
//border-bottom: 1px solid rgba(238,238,238,1);
.jupyter_data_icon{
// color: #7286ff;
color: #1890ff;
@ -24,15 +24,16 @@
.jupyter_data_list,
.jupyter_empty{
height: calc(100vh - 160px);
//height: calc(100vh - 160px);
min-height: 350px;
overflow-y: auto;
}
.jupyter_data_list{
.jupyter_item{
line-height:45px;
border-bottom: 1px solid rgba(238,238,238, 1);
padding: 0 30px 0 60px;
//border-bottom: 1px solid rgba(238,238,238, 1);
padding: 0 30px 0 45px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;

@ -58,6 +58,7 @@ function RightPane (props) {
<div className="jupyter_result">
<div className="jupyter_iframe">
<iframe
id={"rightPaneframe"}
title=" "
width="100%"
height="100%"
@ -65,13 +66,13 @@ function RightPane (props) {
className='jupyter_iframe_style'
></iframe>
</div>
<div className="jupyter_submit">
<Button
loading={loading}
type="primary"
onClick={handleClickSubmit}
>保存</Button>
</div>
{/*<div className="jupyter_submit">*/}
{/* <Button*/}
{/* loading={loading}*/}
{/* type="primary"*/}
{/* onClick={handleClickSubmit}*/}
{/* >保存</Button>*/}
{/*</div>*/}
</div>
));

@ -68,7 +68,7 @@
align-items: center;
height: 56px;
justify-content: flex-end;
padding-right: 30px;
padding-right: 20px;
}
}
}

@ -460,7 +460,7 @@ class Newshixuns extends Component {
{getFieldDecorator('is_jupyter')(
<Radio.Group onChange={this.RadiovalueonChange} value={this.state.Radiovalue}>
<Radio value="1">普通实训</Radio>
{this.props.user&&this.props.user.admin===true||this.props.user&&this.props.user.business===true?<Radio value="2" >Jupyter实训</Radio>:""}
<Radio value="2" >Jupyter实训</Radio>
</Radio.Group>,
)}
</Form.Item>

@ -10,8 +10,6 @@ import { Modal, Spin, Tooltip ,message,Icon,Button,Divider} from 'antd';
import axios from 'axios';
import 'antd/lib/pagination/style/index.css';
import '../shixunchildCss/Challenges.css';
import AccountProfile from"../../../user/AccountProfile";
@ -59,7 +57,7 @@ class Challenges extends Component {
isopentitletype:"Less",
})
}
this.getjianjiesize()
}
}
}).catch((error) => {
@ -67,31 +65,54 @@ class Challenges extends Component {
});
}
getjianjiesize=()=>{
let {ChallengesDataList}=this.state;
let boxoffsetHeigh;
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined){
}else{
if(this.state.isopentitletype==="greater"){
}else{
if(boxoffsetHeigh>=300){
this.setState({
opentitletype:true,
isopentitletype:"greater",
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
isopentitletype:"Less",
boxoffsetHeigh:boxoffsetHeigh
})
}
}
}
}
}
componentDidMount() {
this.ChallengesList()
if(this.state.isopentitletype==="greater"){
}else {
this.ChallengesList()
}
}
componentDidUpdate = (prevProps,prevState) => {
//防止陷入无限循环
if(prevState.ChallengesDataList!=this.state.ChallengesDataList){
let boxoffsetHeigh;
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(boxoffsetHeigh<300){
this.setState({
isopentitletype:"Less",
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
opentitletype:true,
isopentitletype:"greater",
boxoffsetHeigh:boxoffsetHeigh
})
}
console.log(boxoffsetHeigh)
if(this.state.isopentitletype==="greater"){
}else{
this.getjianjiesize()
}
}
@ -333,7 +354,6 @@ class Challenges extends Component {
opentitle=()=>{
this.setState({
opentitletype:!this.state.opentitletype,
})
}
@ -407,24 +427,15 @@ class Challenges extends Component {
<span className="font-16 fl">简介</span>
{this.props.identity < 5 && ChallengesDataList&&ChallengesDataList.shixun_status < 3 ?
<Link to={"/shixuns/" + id + "/settings"} className="fr color-blue font-14">
<a href={"/shixuns/" + id + "/settings?edit=1"} className="fr color-blue font-14">
{/*<img src={getImageUrl("images/educoder/icon/edit.svg")} className="fl mt3 ml2" />*/}
编辑
</Link>:""}
</a>:""}
{this.props.user && this.props.user.main_site === true ?
this.props.identity < 5?<a className="fr font-14 color-blue mr20" href="/forums/2943"
target="_blank">实训制作指南</a> : "":""}
</div>
{this.state.opentitletype===true?<style>
{
`
#shixunchallengesid{
max-height: 300px;
overflow: hidden;
}
`
}
</style>:""}
<div>
<div className={"pd20"} id={"shixunchallengesid"}>
<style>
@ -436,6 +447,40 @@ class Challenges extends Component {
`
}
</style>
<style>
{
`
.markdown-body img{
min-height: 200px;
}
`
}
</style>
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?<style>
{
`
#shixunchallengesid{
max-height: 300px;
overflow: hidden;
}
`
}
</style>:""}
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?"":this.state.isopentitletype==="greater"&&this.state.opentitletype===true?
<style>
{
`
#shixunchallengesid{
max-height:260px;
overflow: hidden;
}
`
}
</style>:""}
<div className="justify break_full_word new_li "
id="challenge_editorMd_description">

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Link } from "react-router-dom";
import { markdownToHTML, configShareForCustom,getImageUrl} from 'educoder'
import { Divider, Tooltip } from 'antd';
import { markdownToHTML, configShareForCustom,getImageUrl,getUploadActionUrlthree,appendFileSizeToUploadFileAll} from 'educoder'
import { Divider, Tooltip,Upload,Spin} from 'antd';
import LoadingSpin from '../../../../common/LoadingSpin';
import 'antd/lib/pagination/style/index.css';
import '../shixunchildCss/Challenges.css';
@ -19,12 +19,16 @@ class Challengesjupyter extends Component {
iFrameHeight: '0px',
jupyter_port:0,
jupyter_url:null,
jupyter_urls:null,
username:"",
booljupyterurls:false,
loading:false,
boxoffsetHeigh:0,
opentitletype:true,
isopentitletype:"Less",
enlarge:false,
fileList:[],
shuaxin:false,
}
}
@ -47,44 +51,66 @@ class Challengesjupyter extends Component {
isopentitletype:"Less",
})
}
this.getjianjiesize()
}
}
}).catch((error) => {
//console.log(error)
});
}
getjianjiesize=()=>{
let {ChallengesDataList}=this.state;
let boxoffsetHeigh;
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined){
}else{
if(this.state.isopentitletype==="greater"){
}else{
if(boxoffsetHeigh>=300){
this.setState({
opentitletype:true,
isopentitletype:"greater",
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
isopentitletype:"Less",
boxoffsetHeigh:boxoffsetHeigh
})
}
}
}
}
}
componentDidUpdate = (prevProps,prevState) => {
//防止陷入无限循环
if(prevState.ChallengesDataList!=this.state.ChallengesDataList){
let boxoffsetHeigh;
let box=document.getElementById("shixunchallengesid");
if(box){
boxoffsetHeigh=box.offsetHeight
if(boxoffsetHeigh<300){
this.setState({
isopentitletype:"Less",
boxoffsetHeigh:boxoffsetHeigh
})
}else{
this.setState({
isopentitletype:"greater",
opentitletype:true,
boxoffsetHeigh:boxoffsetHeigh
})
}
if(this.state.isopentitletype==="greater"){
}else{
this.getjianjiesize()
}
}
}
componentDidMount() {
var that=this;
setTimeout(this.ChallengesList(), 1000);
let id = this.props.match.params.shixunId;
let ChallengesURL = `/jupyters/get_info_with_tpm.json`;
let datas={
identifier:id,
}
let datas={
identifier:id,
}
axios.get(ChallengesURL, {params: datas}).then((response) => {
if (response.data.status === 403||response.data.status === 401||response.data.status === 500) {
setTimeout(() => {
@ -92,26 +118,24 @@ class Challengesjupyter extends Component {
booljupyterurls:true,
})
}, 600)
}else{
if(response.data.status===0){
setTimeout(() => {
this.setState({
jupyter_url:response.data.url,
jupyter_urls:response.data.url,
jupyter_port:response.data.port,
booljupyterurls:true,
})
}, 800)
}else{
if(response.data.status===0){
setTimeout(() => {
this.setState({
jupyter_url:response.data.url,
jupyter_port:response.data.port,
booljupyterurls:true,
})
}, 800)
}else{
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}
setTimeout(() => {
this.setState({
booljupyterurls:true,
})
}, 600)
}
}
}).catch((error) => {
@ -122,6 +146,22 @@ class Challengesjupyter extends Component {
}, 600)
});
setTimeout(this.getjianjiesize(), 1000);
window.addEventListener('message', (e) => {
console.log(e);
if(e){
if(e.data){
if(e.data==="jupytermessage"){
that.modifyjupyter();
}
}
}
});
}
updatamakedowns = () => {
@ -146,6 +186,7 @@ class Challengesjupyter extends Component {
setTimeout(() => {
this.setState({
jupyter_url:response.data.url,
jupyter_urls:response.data.url,
jupyter_port:response.data.port,
booljupyterurls:true,
})
@ -174,14 +215,16 @@ class Challengesjupyter extends Component {
modifyjupyter=()=>{
this.props.showNotification('实训正在保存中...!');
let id=this.props.match.params.shixunId;
var jupyter_port="";
try{
jupyter_port= parseInt(this.state.jupyter_port);
}catch (e) {
jupyter_port=this.state.jupyter_port;
var jupyter_port="";
try{
jupyter_port= parseInt(this.state.jupyter_port);
}catch (e) {
jupyter_port=this.state.jupyter_port;
}
}
const url=`/jupyters/save_with_tpm.json`;
const data={
identifier:id,
@ -190,9 +233,14 @@ class Challengesjupyter extends Component {
axios.get(url, {params: data})
.then((result) => {
if (result.data.status === 0) {
this.props.showNotification(`应用成功`);
// this.props.showNotification(`应用成功`);
console.log("应用成功了");
this.props.showNotification('实训保存成功!');
}else{
this.props.showNotification(result.data.message);
}
}).catch((error) => {
this.props.showNotification('实训保存失败!');
})
}
@ -201,10 +249,75 @@ class Challengesjupyter extends Component {
opentitletype:!this.state.opentitletype
})
}
onclki=(bool)=>{
this.setState({
enlarge:bool
})
}
Importingfiles=()=>{
// 导入文件
}
handleChange = (info) => {
if(info.file.status == "done" || info.file.status == "uploading" || info.file.status === 'removed'){
let fileList = info.fileList;
this.setState({
fileList: appendFileSizeToUploadFileAll(fileList),
});
if(info.file.status === 'done'){
if(info.file.response){
if(info.file.response.status===-1||info.file.response.status==="-1"){
}else{
}
}
}
if(info.file.response){
if(info.file.response.status===-1||info.file.response.status==="-1"){
}else{
if(info.file.response.status===0){
try {
this.modifyjupyter();
}catch (e) {
}
try {
this.setState({
jupyter_url : null,
booljupyterurls:false,
})
setTimeout(()=>{
this.setState({
jupyter_url : this.state.jupyter_urls,
booljupyterurls:true,
})
},1000);
this.props.showNotification('上传文件成功!');
}catch (e) {
this.setState({
jupyter_url : null,
booljupyterurls:false,
})
setTimeout(()=>{
this.setState({
jupyter_url : this.state.jupyter_urls,
booljupyterurls:true,
})
},1000);
}
}
}
}
}
}
render() {
let{ChallengesDataList,booljupyterurls}=this.state;
let{ChallengesDataList,booljupyterurls,enlarge,fileList}=this.state;
let id = this.props.match.params.shixunId;
//老师
const is_teacher = this.props&&this.props.current_user&&this.props.current_user.is_teacher?this.props.current_user.is_teacher:false;
@ -219,7 +332,35 @@ class Challengesjupyter extends Component {
}catch (e) {
}
const uploadProps = {
width: 600,
fileList,
multiple: false,
data:{
identifier:id,
},
//multiple 是否支持多选 查重的时候不能多选 不然弹许多框出来
// https://github.com/ant-design/ant-design/issues/15505
// showUploadList={false},然后外部拿到 fileList 数组自行渲染列表。
// showUploadList: false,
action: `${getUploadActionUrlthree()}`,
showUploadList:false,
onChange: this.handleChange,
beforeUpload: (file) => {
//上传前的操作
// console.log('beforeUpload', file.name);
if(file.name.indexOf('.ipynb') === -1){
this.props.showNotification('请上传10m以内ipynb格式的文件!');
return false
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
this.props.showNotification('请上传10m以内ipynb格式的文件!');
}
return isLt10M;
},
};
return (
<React.Fragment>
<div className="">
@ -227,23 +368,45 @@ class Challengesjupyter extends Component {
<div className={"shixunjianjie"}>
<span className="font-16 fl">简介</span>
<Tooltip placement="bottom" title={"编辑"}>
<Link style={{ display: this.props.identity < 5 && ChallengesDataList&&ChallengesDataList.shixun_status < 3 ? "block" : 'none' }}
to={"/shixuns/" + id + "/settings?edit=1"} className="fr color-blue font-14">
<a style={{ display: this.props.identity < 5 && ChallengesDataList&&ChallengesDataList.shixun_status < 3 ? "block" : 'none' }}
href={"/shixuns/" + id + "/settings?edit=1"} className="fr color-blue font-14">
编辑
</Link>
</a>
</Tooltip>
</div>
{this.state.opentitletype===true?<style>
{
`
<div>
<style>
{
`
.markdown-body img{
min-height: 200px;
}
`
}
</style>
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?<style>
{
`
#shixunchallengesid{
max-height: 300px;
overflow: hidden;
}
`
}
</style>:""}
<div>
}
</style>:""}
{ChallengesDataList === undefined || ChallengesDataList&&ChallengesDataList.description=== ""||ChallengesDataList&&ChallengesDataList.description===null||ChallengesDataList&&ChallengesDataList.description===undefined?"":this.state.isopentitletype==="greater"&&this.state.opentitletype===true?
<style>
{
`
#shixunchallengesid{
max-height:260px;
overflow: hidden;
}
`
}
</style>:""}
<div className={"pd20"} id={"shixunchallengesid"}>
<p id="ReactMarkdown" style={{overflow:'hidden'}}>
@ -320,18 +483,48 @@ class Challengesjupyter extends Component {
""
:
(
admin===true||business===true||mysidentity===true?
<div className={"shixunjianjiecballenges edu-back-white sortinxdirection mt20"}>
<div className="renwuxiangssi sortinxdirection">
<div><p className="renwuxiangqdiv">任务详情</p></div>
<div><p className="renwuxiangqdivtest ml1 shixunbingbaocun">请将实训题目写在下方并保存</p></div>
</div>
<div className="renwuxiangssit xaxisreverseorder">
<div className="challenbaocun" onClick={() => this.modifyjupyter(this.state)}><p
className="challenbaocuntest">应用到实训</p></div>
admin===true||business===true||mysidentity===true?
<div style={{
height: '63px',
}} className={enlarge?"shixunjianjiecballenges edu-back-white intermediatecenter fangdaone":"shixunjianjiecballenges edu-back-white mt20"}>
<div className={enlarge?"sortinxdirection jupyterswidth":"sortinxdirection"} >
<div className="renwuxiangssi sortinxdirection">
<div><p className="renwuxiangqdiv">任务详情</p></div>
<div><p className="renwuxiangqdivtest ml1 shixunbingbaocun">请将实训题目写在下方并保存</p></div>
</div>
<div className="renwuxiangssit xaxisreverseorder">
{
enlarge===true?
<i className="iconfont icon-suoxiao2 font-18 ml2 ysliconfont" style={{
marginLeft: '30px',
}} onClick={()=>this.onclki(false)}></i>
:
<i className="iconfont icon-fangda font-18 ml2 ysliconfont" style={{
marginLeft: '30px',
}} onClick={()=>this.onclki(true)}></i>
}
<style>
{
`
.ant-upload-list{
display:none
}
`
}
</style>
<Upload {...uploadProps}>
<div className="challenbaocun" type="upload">
<p
className="challenbaocuntest" type="upload" >导入</p>
</div>
</Upload>
</div>
</div>
</div>
:
:
""
)
@ -348,7 +541,6 @@ class Challengesjupyter extends Component {
}
iframe {
border-left: 1px solid #eeeeee;
border-top: 1px solid #eeeeee;
border-right: 1px solid #eeeeee;
border-bottom: 1px solid #eeeeee;
}
@ -363,25 +555,26 @@ class Challengesjupyter extends Component {
}
</style>
{
admin===true||business===true||mysidentity===true?
<div>
<div className="pb47">
{
this.state.jupyter_url===null || this.state.jupyter_url===undefined?
(
booljupyterurls===false?
<LoadingSpin></LoadingSpin>
:""
)
:
<iframe src={this.state.jupyter_url}
sandbox="allow-same-origin allow-scripts allow-top-navigation " scrolling="no" id="frame"
name="framename" width="100%" height="700" frameBorder="0"
></iframe>
}
</div>
admin===true||business===true||mysidentity===true?
<div>
<div className="pb47">
{
this.state.jupyter_url===null || this.state.jupyter_url===undefined?
(
booljupyterurls===false?
<LoadingSpin></LoadingSpin>
:""
)
:
<iframe src={this.state.jupyter_url} className={enlarge?"fangdatwo":""}
scrolling="no" id="frame"
name="framename" width="100%" height="700" frameBorder="0"
></iframe>
}
</div>
:""
</div>
:""
}
</div>
</div>

@ -330,6 +330,7 @@ class Collaborators extends Component {
});
this.updatacomponentDiddata();
this.props.showNotification(response.data.message);
// window.location.reload();
}).catch((error) => {
console.log(error)
});
@ -530,7 +531,7 @@ class Collaborators extends Component {
{
Collaboratorslist.length === 0 ? "" : Collaboratorslist.map((item, key) => {
return (
<Radio key={key} style={radioStyle} value={item.user_id}
<Radio key={key} style={radioStyle} value={item.user_id} defaultChecked={false}
onClick={() => this.addadminredio(item.user_id)}>{item.name}</Radio>
)
})

@ -27,14 +27,14 @@
line-height: 25px;
}
.challenbaocun{
width:103px;
width:60px;
height:30px;
background:#29BD8B;
border-radius:3px;
cursor:pointer
}
.challenbaocuntest{
width:103px;
width:60px;
height:30px;
font-size:16px;
color:#FFFFFF;
@ -131,7 +131,6 @@
height: 76px;
line-height: 35px;
padding: 20px;
border-bottom: 1px solid #eeee;
}
.padding1020pxshixun{
@ -185,4 +184,34 @@
.icon-shanchu_Hover:hover{
color:rgb(255, 85, 85) !important;
}
.ysliconfont{
text-align: center;
line-height: 29px;
color: #8a8a8a;
}
.fangdaone{
height: 63px;
width: 100%;
position: fixed;
top: 0px;
left: 0px;
z-index: 999999;
right: 0px;
}
.fangdatwo{
height: 100%;
width: 100%;
position: fixed;
top:0px;
margin-top: 63px;
bottom: 0px;
left: 0px;
z-index: 999999;
right: 0px;
}
.jupyterswidth{
width: 1140px;
}

@ -51,6 +51,8 @@ const types = {
SAVE_USER_INFO: 'SAVE_USER_INFO', // 只在用户信息
SAVE_HACK_IDENTIFIER: 'SAVE_HACK_IDENTIFIER', // 用户界面跑到编辑界面需要用的id值
SAVE_EDITOR_CODE: 'SAVE_EDITOR_CODE', // 保存详情页面中编辑时的代码
CLICK_OPERATE_TYPE: 'CLICK_OPERATE_TYPE', // 点击类型
CLEAR_OJ_FOR_USER_REDUCER: 'CLEAR_OJ_FOR_USER_REDUCER', // 退出时清空 ojForUserReducer保存内容
/*** jupyter */
GET_JUPYTER_DATA_SETS: 'GET_JUPYTER_DATA_SETS', // jupyter 数据集
GET_JUPYTER_TPI_URL: 'GET_JUPYTER_TPI_URL', // 获取 jupyter url
@ -61,6 +63,10 @@ const types = {
CHANGE_JUPYTER_CURRENT_PAGE: 'CHANGE_JUPYTER_CURRENT_PAGE',
SAVE_NOTICE_COUNT: 'SAVE_NOTICE_COUNT', // 保存代码块是否更新
AUTO_UPDATE_CODE: 'AUTO_UPDATE_CODE', // 自动更新代码
CHANGE_SHOW_DRAWER: 'CHANGE_SHOW_DRAWER',
CHANGE_JYPYTER_TIME: 'CHANGE_JYPYTER_TIME',//增加15分钟
CHANGE_EXTENDED_TIME: 'CHANGE_EXTENDED_TIME',//延时
CHANGE_UPDETA_SPIN: 'CHANGE_UPDETA_SPIN',//加载
}
export default types;

@ -48,6 +48,8 @@ import {
restoreInitialCode,
saveUserCodeForInterval,
saveEditorCodeForDetail,
saveOpacityType,
clearOjForUserReducer
// isUpdateCodeCtx
} from './ojForUser';
@ -70,7 +72,12 @@ import {
syncJupyterCode,
changeGetJupyterUrlState,
saveJupyterTpi,
changeCurrentPage
changeCurrentPage,
changeshowDrawer,
reset_with_tpi,
addjypertime,
active_with_tpi,
updataspinning
} from './jupyter';
export default {
@ -116,6 +123,8 @@ export default {
getUserInfoForNew,
saveUserCodeForInterval,
saveEditorCodeForDetail,
saveOpacityType,
clearOjForUserReducer,
// jupyter
getJupyterTpiDataSet,
getJupyterTpiUrl,
@ -123,6 +132,11 @@ export default {
syncJupyterCode,
changeGetJupyterUrlState,
saveJupyterTpi,
changeCurrentPage
changeCurrentPage,
changeshowDrawer,
reset_with_tpi,
addjypertime,
active_with_tpi,
updataspinning
// isUpdateCodeCtx
}

@ -13,7 +13,9 @@ import {
fetchJupyterTpiUrl,
fetchJupyterInfo,
fetchSyncJupyterCode,
fetchSaveJupyterTpi
fetchreset_with_tpi,
fetchSaveJupyterTpi,
fetactive_with_tpi
} from "../../services/jupyterServer";
// 获取 jupyter 相关信息
@ -73,6 +75,7 @@ export const getJupyterTpiUrl = (obj) => {
console.log('获取url', res);
if (res.status === 200) {
const { status, url = '', port } = res.data;
addjypertime(Date.now() +3600 * 1000)
dispatch({
type: types.GET_JUPYTER_TPI_URL,
payload: {
@ -102,6 +105,7 @@ export const syncJupyterCode = (identifier, msg) => {
const {status} = res.data
if (status === 0) {
message.success(msg);
updataspinning(false)
setTimeout(() => {
window.location.reload();
}, 300);
@ -110,6 +114,55 @@ export const syncJupyterCode = (identifier, msg) => {
})
}
}
// 重置环境
export const reset_with_tpi = (identifier, msg) => {
return (dispatch,getState) => {
const {jupyter_info }= getState().jupyterReducer;
if (!jupyter_info.myshixun_identifier) return;
const params = {
identifier: jupyter_info.myshixun_identifier,
};
fetchreset_with_tpi(params).then(res => {
// console.log('同步代码成功: ', res);
if (res.data.status === 401) return;
if (res.status === 200) {
const {status} = res.data
if (status === 0) {
message.success(msg);
updataspinning(false)
setTimeout(() => {
window.location.reload();
}, 300);
}
}
})
}
}
// 延时
export const active_with_tpi = (identifier, msg) => {
return (dispatch,getState) => {
const {jupyter_info }= getState().jupyterReducer;
if (!jupyter_info.myshixun_identifier) return;
const params = {
identifier: jupyter_info.myshixun_identifier,
};
fetactive_with_tpi(params).then(res => {
// console.log('同步代码成功: ', res);
if (res.data.status === 401) return;
if (res.status === 200) {
const {status} = res.data
if (status === 0) {
message.success(msg);
addjypertime(Date.now() + 900 * 1000);
}
}
})
}
}
// 改变状态值
export const changeGetJupyterUrlState = (status) => {
return {
@ -155,3 +208,26 @@ export const changeCurrentPage = (current) => {
payload: current
}
}
// 改变当前页数
export const changeshowDrawer = (type) => {
return {
type: types.CHANGE_SHOW_DRAWER,
payload: type
}
}
//增加倒计时
export const addjypertime=(time)=>{
return {
type: types.CHANGE_JYPYTER_TIME,
payload: time
}
}
export const updataspinning=(type)=>{
return {
type: types.CHANGE_UPDETA_SPIN,
payload: type
}
}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 13:42:11
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 15:11:56
* @LastEditTime: 2019-12-20 19:30:30
*/
import types from "./actionTypes";
import { Base64 } from 'js-base64';
@ -171,6 +171,98 @@ export const updateCode = (identifier, inputValue, type) => {
}
}
// 代码评测
export const codeEvaluate = (dispatch, identifier, type, time_limit) => {
// 调试代码成功后,调用轮循接口, 注意: 代码执行的时间要小于设置的时间限制
const intervalTime = 500;
let count = 1;
/**
* @param {*} excuteTime 执行时间
* @param {*} finalTime 总时间
* @param {*} count 执行次数
* @param {*} timer 定时器
*/
function getCodeSubmit (intervalTime, finalTime, count, timer){
const excuteTime = (count++) * intervalTime; // 当前执行时间
fetchCodeSubmit(identifier, { mode: type }).then(res => {
const { status } = res.data; // 评测返回结果
// 清除定时器条件: 评测通过或者评测时间大于指定时间
if (+status === 0 || (excuteTime / 1000) > (finalTime + 1)) {
clearInterval(timer); // 清除定时器
timer = null;
let returnData = null;
if (status === 1) { // 结果没有返回
returnData = {
error_line: -1,
error_msg: '',
execute_memory: '',
execute_time: finalTime,
input: '',
output: '',
status: 2,
expected_output: ''
};
} else { // 成功返回结果
returnData = res.data.data;
}
// 返回评测结果
dispatch({
type: types.COMMIT_RECORD_DETAIL,
payload: {
type,
data: returnData
}
});
if (!type || type === 'debug') {
dispatch({ // 改变 loading 值
type: types.LOADING_STATUS,
payload: false
});
// 保存执行状态
dispatch({
type: types.TEST_CODE_STATUS,
payload: 'finish'
});
} else {
// 回滚提交按钮状态
dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
});
// 改变tab值至提交记录(只在提交时才跳转,测评时,切换到代码执行结果就可以了)
dispatch({
type: types.CHANGE_USER_CODE_TAB,
payload: 'record'
});
// 重新调用一下提交记录接口
dispatch(getUserCommitRecord(identifier));
dispatch(saveOpacityType(type));
}
}
}).catch(err => { // 评测异常时
// 清除定时器
clearInterval(timer);
timer = null;
// 回滚按钮状态
if (!type || type === 'debug') {
dispatch({ // 改变 loading 值
type: types.LOADING_STATUS,
payload: false
});
} else { // 回滚提交按钮状态
dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
});
}
});
}
// 开启定时器,调用监听接口
let timer = setInterval(() => {
getCodeSubmit(intervalTime, time_limit, count++, timer);
}, intervalTime);
}
/**
* @description 调试代码
* @param {*} identifier
@ -194,15 +286,6 @@ export const debuggerCode = (identifier,value, type) => {
// console.log('调用调试代码成功并返回结果: ', res);
const { status } = res;
if (status === 200) {
// 调试代码成功后,调用轮循接口, 注意: 代码执行的时间要小于设置的时间限制
const intervalTime = 500;
let count = 1;
/**
* @param {*} excuteTime 执行时间
* @param {*} finalTime 总时间
* @param {*} count 执行次数
* @param {*} timer 定时器
*/
if (res.data.status === 401) {
dispatch({ // 改变 loading 值
type: types.LOADING_STATUS,
@ -210,81 +293,8 @@ export const debuggerCode = (identifier,value, type) => {
});
return;
};
function getCodeSubmit (intervalTime, finalTime, count, timer){
const excuteTime = (count++) * intervalTime; // 当前执行时间
fetchCodeSubmit(identifier, { mode: type }).then(res => {
const { status } = res.data; // 评测返回结果
// 清除定时器条件: 评测通过或者评测时间大于指定时间
if (+status === 0 || (excuteTime / 1000) > (finalTime + 1)) {
clearInterval(timer); // 清除定时器
timer = null;
let returnData = null;
if (status === 1) { // 结果没有返回
returnData = {
error_line: -1,
error_msg: '',
execute_memory: '',
execute_time: finalTime,
input: '',
output: '',
status: 2,
expected_output: ''
};
} else { // 成功返回结果
returnData = res.data.data;
}
// 返回评测结果
dispatch({
type: types.COMMIT_RECORD_DETAIL,
payload: returnData
});
if (!type || type === 'debug') {
dispatch({ // 改变 loading 值
type: types.LOADING_STATUS,
payload: false
});
// 保存执行状态
dispatch({
type: types.TEST_CODE_STATUS,
payload: 'finish'
});
} else {
// 回滚提交按钮状态
dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
});
// 改变tab值至提交记录(只在提交时才跳转,测评时,切换到代码执行结果就可以了)
dispatch({
type: types.CHANGE_USER_CODE_TAB,
payload: 'record'
});
// 重新调用一下提交记录接口
dispatch(getUserCommitRecord(identifier));
}
}
}).catch(err => { // 评测异常时
// 清除定时器
clearInterval(timer);
timer = null;
// 回滚按钮状态
if (!type || type === 'debug') {
dispatch({ // 改变 loading 值
type: types.LOADING_STATUS,
payload: false
});
} else { // 回滚提交按钮状态
dispatch({
type: types.SUBMIT_LOADING_STATUS,
payload: false
});
}
});
}
// 开启定时器,调用监听接口
let timer = setInterval(() => {
getCodeSubmit(intervalTime, time_limit, count++, timer);
}, intervalTime);
// 测评
codeEvaluate(dispatch, identifier, type, time_limit);
}
}).catch(() => {
dispatch({
@ -328,6 +338,10 @@ export const getUserCommitRecordDetail = (identifier) => {
type: types.GET_COMMIT_RECORD_DETAIL_BY_ID,
payload: data
});
dispatch({
type: types.CLICK_OPERATE_TYPE,
payload: ''
});
});
}
}
@ -362,7 +376,8 @@ export const changeUserCodeTab = (key) => {
*/
export const submitUserCode = (identifier, inputValue, type) => {
return (dispatch, getState) => {
const { userCode, isUpdateCode } = getState().ojForUserReducer;
const { userCode, isUpdateCode, hack: {time_limit = 0} } = getState().ojForUserReducer;
function userCodeSubmit () {
fetchUserCodeSubmit(identifier).then(res => {
// console.log('用户提交代码成功======》》》》》', res);
@ -374,12 +389,8 @@ export const submitUserCode = (identifier, inputValue, type) => {
});
return;
};
// 将编辑代码清空
dispatch({
type: types.SAVE_EDITOR_CODE,
payload: ''
});
dispatch(debuggerCode(identifier, inputValue, type || 'submit'));
// 测评
codeEvaluate(dispatch, identifier, type, time_limit);
}
}).catch(() => {
dispatch({
@ -451,5 +462,20 @@ export const saveEditorCodeForDetail = (code) => {
}
}
// 保存操作类型: 提交或调试
export const saveOpacityType = (type) => {
return {
type: types.CLICK_OPERATE_TYPE,
payload: type
}
}
export const clearOjForUserReducer = () => {
return {
type: types.CLEAR_OJ_FOR_USER_REDUCER
};
}
// 更新通知状态

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-20 16:35:46
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 17:20:48
* @LastEditTime: 2019-12-20 19:54:09
*/
import types from './actionTypes';
import CONST from '../../constants';
@ -135,7 +135,7 @@ export const validateOjForm = (props, type) => {
} else {
// 唯一性校验
const bool = testCases.some((item, j) => {
if (i !== j) {
if (i > j && key === 'input') {
return (item[key] === value);
} else {
return false;
@ -147,7 +147,7 @@ export const validateOjForm = (props, type) => {
validateResult = {
[key]: {
validateStatus: 'error',
errMsg: key === 'input' ? '输入值必须是唯一的' : '输出值必须是唯一的'
errMsg: `与测试用例${i}的输入值重复了,请重新填写`
}
};
}
@ -481,48 +481,48 @@ export const testCaseInputChange = (value, index) => {
// 测试用例输出值改变时
export const testCaseOutputChange = (value, index) => {
// const validate = emptyValidate('output', value)['output'];
// return {
// type: types.TEST_CASE_OUTPUT_CHANGE,
// payload: {
// output: validate,
// value,
// index
// }
// }
return (dispatch, getState) => {
// 非空校验
let validate = emptyValidate('output', value)['output'];
if (!validate.errMsg) {
// 唯一性校验
const {testCases} = getState().ojFormReducer;
let _errMsg = '';
const bool = testCases.some((item, i) => {
if (i !== index) {
if (item['output'] === value) {
_errMsg=`与测试用例${index}的输入值重复了,请重新填写`;
}
return item['output'] === value;
} else {
return false;
}
});
if (bool) {
validate = {
validateStatus: 'error',
errMsg: _errMsg
};
}
const validate = emptyValidate('output', value)['output'];
return {
type: types.TEST_CASE_OUTPUT_CHANGE,
payload: {
output: validate,
value,
index
}
dispatch({
type: types.TEST_CASE_OUTPUT_CHANGE,
payload: {
output: validate,
value,
index
}
});
}
// return (dispatch, getState) => {
// // 非空校验
// let validate = emptyValidate('output', value)['output'];
// if (!validate.errMsg) {
// // 唯一性校验
// const {testCases} = getState().ojFormReducer;
// let _errMsg = '';
// const bool = testCases.some((item, i) => {
// if (i !== index) {
// // if (item['output'] === value) {
// // _errMsg=`与测试用例${index}的输入值重复了,请重新填写`;
// // }
// return item['output'] === value;
// } else {
// return false;
// }
// });
// if (bool) {
// validate = {
// validateStatus: 'error',
// errMsg: _errMsg
// };
// }
// }
// dispatch({
// type: types.TEST_CASE_OUTPUT_CHANGE,
// payload: {
// output: validate,
// value,
// index
// }
// });
// }
}
// // 调试代码时,更改对应的状态值

@ -14,7 +14,10 @@ const initialState = {
excuteState: '', // 代码执行状态
submitLoading: false, // 提交按钮状态
publishLoading: false, // 发布
isMySource: false
isMySource: false,
drawervisible:false,
jupytertime:Date.now() + 3600 * 1000,
spinning:false
}
const commonReducer = (state = initialState, action) => {
@ -50,6 +53,21 @@ const commonReducer = (state = initialState, action) => {
...state,
isMySource: action.payload
}
case types.CHANGE_SHOW_DRAWER:
return {
...state,
drawervisible: action.payload
}
case types.CHANGE_JYPYTER_TIME:
return {
...state,
jupytertime: action.payload
}
case types.CHANGE_UPDETA_SPIN:
return {
...state,
spinning: action.payload
}
default:
return state;
}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-27 13:41:48
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-19 20:10:39
* @LastEditTime: 2019-12-20 14:46:07
*/
import types from "../actions/actionTypes";
import { Base64 } from 'js-base64';
@ -13,6 +13,7 @@ const initialState = {
user_program_identifier: '', // 开启OJ题的唯一标题
hack: {}, // 编程题主要内容
test_case: {}, // 测试用例
commitTestRecordDetail: {}, // 调试代码执行结果
commitRecordDetail: {}, // 提交成功后记录提交的详情
commitRecord: [], // 提交记录
userCode: '', // 保存当前用户输入的代码
@ -24,6 +25,7 @@ const initialState = {
editor_code: '', // 保存编辑代码
notice: false, // 通知
hadCodeUpdate: false, // 更新代码
operateType: '', // 点击类型: 调度或提交
};
const ojForUserReducer = (state = initialState, action) => {
@ -51,7 +53,7 @@ const ojForUserReducer = (state = initialState, action) => {
test_case: Object.assign({}, test_case)
}
case types.COMMIT_RECORD_DETAIL:
let result = action.payload;
let result = action.payload.data;
if (result['expected_output']) {
result['expected_output'] = Base64.decode(result['expected_output'])
}
@ -63,10 +65,18 @@ const ojForUserReducer = (state = initialState, action) => {
} catch (e) {
console.log('错误信息:', e);
}
return {
...state,
commitRecordDetail: Object.assign({}, result)
if (action.payload.type === 'submit') {
return {
...state,
commitRecordDetail: Object.assign({}, result)
}
} else {
return {
...state,
commitTestRecordDetail: Object.assign({}, result)
}
}
case types.COMMIT_RECORD:
return {
...state,
@ -144,6 +154,31 @@ const ojForUserReducer = (state = initialState, action) => {
...state,
hadCodeUpdate: action.payload
};
case types.CLICK_OPERATE_TYPE:
return {
...state,
operateType: action.payload
}
case types.CLEAR_OJ_FOR_USER_REDUCER:
return {
...state,
user_program_identifier: '', // 开启OJ题的唯一标题
hack: {}, // 编程题主要内容
test_case: {}, // 测试用例
commitTestRecordDetail: {}, // 调试代码执行结果
commitRecordDetail: {}, // 提交成功后记录提交的详情
commitRecord: [], // 提交记录
userCode: '', // 保存当前用户输入的代码
isUpdateCode: false, // 是否更新了代码内容
userCodeTab: 'task', // 学员测评tab位置: task | record | comment
userTestInput: '', // 用户自定义输入值
recordDetail: {}, // 根据id号获取的记录详情
hack_identifier: '', // 用户界面编辑时
editor_code: '', // 保存编辑代码
notice: false, // 通知
hadCodeUpdate: false, // 更新代码
operateType: '', // 点击类型: 调度或提交
};
default:
return state;
}

@ -4,7 +4,7 @@
* @Github:
* @Date: 2019-11-20 16:40:32
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-17 16:19:04
* @LastEditTime: 2019-12-20 16:40:52
*/
import { Base64 } from 'js-base64';
import types from '../actions/actionTypes';
@ -14,8 +14,8 @@ const init = {
name: '', // 任务名称
language: '',
description: '',
difficult: 1,
category: 1,
difficult: '',
category: '',
// openOrNot: 1,
timeLimit: 3
},

@ -33,3 +33,15 @@ export async function fetchSaveJupyterTpi (params) {
const url = `/jupyters/save_with_tpi.json`;
return axios.get(url, { params });
}
//重置jupyter 环境
export async function fetchreset_with_tpi (params) {
const url = `/jupyters/reset_with_tpi.json`;
return axios.get(url, { params });
}
//延时jupyter
export async function fetactive_with_tpi(params) {
const url = `/jupyters/active_with_tpi.json`;
return axios.get(url, { params });
}

@ -4,11 +4,10 @@
* @Github:
* @Date: 2019-11-20 10:55:38
* @LastEditors: tangjiang
* @LastEditTime: 2019-12-17 14:10:37
* @LastEditTime: 2019-12-20 10:10:53
*/
import axios from 'axios';
import { func } from 'prop-types';
export async function fetchOJList (params) {
console.log('传递的参数: ', params);

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save