init project
107
public/react/src/App.css
Normal file
@@ -0,0 +1,107 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #222;
|
||||
height: 150px;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-title {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.App-intro {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* 控制md编辑器列行的宽度
|
||||
见 codermirror maybeUpdateLineNumberWidth方法
|
||||
*/
|
||||
.editormd .CodeMirror-linenumbers {
|
||||
padding: 0;
|
||||
}
|
||||
.editormd-html-preview hr, .editormd-preview-container hr {
|
||||
/* 颜色加深 */
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
/* 重置掉antd的一些样式 */
|
||||
html, body {
|
||||
-webkit-font-smoothing: auto !important;
|
||||
}
|
||||
|
||||
.ant-progress-textyes {
|
||||
color: #52c41a;
|
||||
}
|
||||
.ant-progress-textno{
|
||||
color: #f5222d;
|
||||
}
|
||||
/* md多空格 */
|
||||
.markdown-body p {
|
||||
white-space: pre-wrap;
|
||||
font-size: 16px!important
|
||||
}
|
||||
.markdown-body > p {
|
||||
line-height: 25px;
|
||||
}
|
||||
/* https://www.educoder.net/courses/2346/group_homeworks/34405/question */
|
||||
.renderAsHtml.markdown-body p {
|
||||
white-space: inherit;
|
||||
}
|
||||
/* resize */
|
||||
.editormd .CodeMirror {
|
||||
border-right: none !important;
|
||||
}
|
||||
.editormd-preview {
|
||||
border-left: 1px solid rgb(221, 221, 221);
|
||||
/* 某些情况下,被cm盖住了 */
|
||||
z-index: 99;
|
||||
}
|
||||
/* 图片点击放大的场景,隐藏图片链接 */
|
||||
.editormd-image-click-expand .editormd-image-dialog {
|
||||
height: 234px !important;
|
||||
}
|
||||
.editormd-image-click-expand .editormd-image-dialog .image-link {
|
||||
display: none;
|
||||
}
|
||||
/* 解决鼠标框选时,左边第一列没高亮的问题 */
|
||||
.CodeMirror .CodeMirror-lines pre.CodeMirror-line, .CodeMirror .CodeMirror-lines pre.CodeMirror-line-like {
|
||||
padding: 0 12px ;
|
||||
}
|
||||
|
||||
|
||||
/* antd扩展 */
|
||||
.formItemInline.ant-form-item {
|
||||
display: flex;
|
||||
}
|
||||
.formItemInline .ant-form-item-control-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
/* AutoComplete placeholder 不显示的问题 */
|
||||
.ant-select-auto-complete.ant-select .ant-select-selection__placeholder {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
|
||||
/* 兼容性 */
|
||||
/* 火狐有滚动条时高度问题 */
|
||||
@-moz-document url-prefix() {
|
||||
.newContainers {
|
||||
min-height: calc(100% - 60px) !important;
|
||||
}
|
||||
}
|
||||
909
public/react/src/App.js
Normal file
@@ -0,0 +1,909 @@
|
||||
import React, {Component} from 'react';
|
||||
import './public-path';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import {ConfigProvider} from 'antd'
|
||||
import zhCN from 'antd/lib/locale-provider/zh_CN';
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Route,
|
||||
Switch
|
||||
} from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import '@icedesign/base/dist/ICEDesignBase.css';
|
||||
|
||||
import '@icedesign/base/index.scss';
|
||||
|
||||
import LoginDialog from './modules/login/LoginDialog';
|
||||
import Notcompletedysl from './modules/user/Notcompletedysl';
|
||||
import Trialapplicationysl from './modules/login/Trialapplicationysl';
|
||||
import Trialapplicationreview from './modules/user/Trialapplicationreview';
|
||||
import Addcourses from "./modules/courses/coursesPublic/Addcourses";
|
||||
import AccountProfile from "./modules/user/AccountProfile";
|
||||
import Accountnewprofile from './modules/user/Accountnewprofile';
|
||||
import Trialapplication from './modules/login/Trialapplication';
|
||||
import Certifiedprofessional from './modules/modals/Certifiedprofessional';
|
||||
import NotFoundPage from './NotFoundPage'
|
||||
|
||||
import Loading from './Loading'
|
||||
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
|
||||
import moment from 'moment'
|
||||
|
||||
import {MuiThemeProvider, createMuiTheme} from 'material-ui/styles';
|
||||
|
||||
// import './AppConfig'
|
||||
|
||||
import history from './history';
|
||||
|
||||
import {SnackbarHOC} from 'educoder'
|
||||
import {initAxiosInterceptors} from './AppConfig'
|
||||
import { Provider } from 'react-redux';
|
||||
import configureStore from './redux/stores/configureStore';
|
||||
// !!!tpi需要这个来加载css
|
||||
import {TPMIndexHOC} from './modules/tpm/TPMIndexHOC';
|
||||
const store = configureStore();
|
||||
|
||||
const theme = createMuiTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#4CACFF',
|
||||
contrastText: 'rgba(255, 255, 255, 0.87)'
|
||||
},
|
||||
secondary: {main: '#4CACFF'}, // #11cb5f This is just green.A700 as hex.
|
||||
},
|
||||
});
|
||||
//
|
||||
// const Trialapplication= Loadable({
|
||||
// loader: () =>import('./modules/login/Trialapplication'),
|
||||
// loading:Loading,
|
||||
// })
|
||||
//登入
|
||||
const EducoderLogin = Loadable({
|
||||
loader: () => import('./modules/login/EducoderLogin'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
|
||||
//微信登录
|
||||
const Otherlogin=Loadable({
|
||||
loader: () => import('./modules/login/Otherlogin'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
//微信登录
|
||||
const Loginqq=Loadable({
|
||||
loader: () => import('./modules/login/Loginqq'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
|
||||
const Otherloginstart=Loadable({
|
||||
loader: () => import('./modules/login/Otherloginstart'),
|
||||
loading: Loading,
|
||||
})
|
||||
const Otherloginsqq=Loadable({
|
||||
loader: () => import('./modules/login/Otherloginqq'),
|
||||
loading: Loading,
|
||||
})
|
||||
// const TestIndex = Loadable({
|
||||
// loader: () => import('./modules/test'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
|
||||
const IndexWrapperComponent = Loadable({
|
||||
loader: () => import('./modules/page/IndexWrapper'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
const CommentComponent = Loadable({
|
||||
loader: () => import('./modules/comment/CommentContainer'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
// const TestMaterialDesignComponent = Loadable({
|
||||
// loader: () => import('./modules/test/md/TestMaterialDesign'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
// const TestCodeMirrorComponent = Loadable({
|
||||
// loader: () => import('./modules/test/codemirror/TestCodeMirror'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
|
||||
// const TestComponent = Loadable({
|
||||
// loader: () => import('./modules/test/TestRC'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
// const TestUrlQueryComponent = Loadable({
|
||||
// loader: () => import('./modules/test/urlquery/TestUrlQuery'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
|
||||
const TPMIndexComponent = Loadable({
|
||||
loader: () => import('./modules/tpm/TPMIndex'),
|
||||
loading: Loading,
|
||||
})
|
||||
const TPMShixunsIndexComponent = Loadable({
|
||||
loader: () => import('./modules/tpm/shixuns/ShixunsIndex'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
//实训课程(原实训路径)
|
||||
const ShixunPaths = Loadable({
|
||||
loader: () => import('./modules/paths/Index'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
//在线课堂
|
||||
const CoursesIndex = Loadable({
|
||||
loader: () => import('./modules/courses/Index'),
|
||||
loading: Loading,
|
||||
})
|
||||
const SearchPage = Loadable({
|
||||
loader: () => import('./search/SearchPage'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
// 课堂讨论
|
||||
// const BoardIndex = Loadable({
|
||||
// loader: () => import('./modules/courses/boards/BoardIndex'),
|
||||
// loading:Loading,
|
||||
// })
|
||||
|
||||
// //课堂普通作业&分组作业
|
||||
// const CoursesWorkIndex = Loadable({
|
||||
// loader: () => import('./modules/courses/busyWork/Index'),
|
||||
// loading:Loading,
|
||||
// })
|
||||
//
|
||||
|
||||
// const TPMShixunchildIndexComponent = Loadable({
|
||||
// loader: () => import('./modules/tpm/shixunchild/ShixunChildIndex'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
|
||||
|
||||
// const TPMshixunfork_listIndexComponent = Loadable({
|
||||
// loader: () => import('./modules/tpm/shixunchild/Shixunfork_list'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
|
||||
|
||||
const ForumsIndexComponent = Loadable({
|
||||
loader: () => import('./modules/forums/ForumsIndex'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
const ProjectIndex = Loadable({
|
||||
loader: () => import('./forge/Index'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
// trustie plus forum
|
||||
// const TPForumsIndexComponent = Loadable({
|
||||
// loader: () => import('./modules/tp-forums/TPForumsIndex'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
|
||||
|
||||
// const TestPageComponent = Loadable({
|
||||
// loader: () => import('./modules/page/Index'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
|
||||
|
||||
//新建实训
|
||||
const Newshixuns = Loadable({
|
||||
loader: () => import('./modules/tpm/newshixuns/Newshixuns'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
|
||||
//实训首页
|
||||
const ShixunsHome = Loadable({
|
||||
loader: () => import('./modules/home/shixunsHome'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
|
||||
const CompatibilityPageLoadable = Loadable({
|
||||
loader: () => import('./modules/common/CompatibilityPage'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
//403页面
|
||||
const Shixunauthority = Loadable({
|
||||
loader: () => import('./modules/403/Shixunauthority'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
|
||||
//404页面
|
||||
const Shixunnopage = Loadable({
|
||||
loader: () => import('./modules/404/Shixunnopage'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
//500页面
|
||||
const http500 = Loadable({
|
||||
loader: () => import('./modules/500/http500'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
// 登录注册
|
||||
const LoginRegisterPage = Loadable({
|
||||
loader: () => import('./modules/user/LoginRegisterPage'),
|
||||
loading: Loading,
|
||||
})
|
||||
const AccountPage = Loadable({
|
||||
loader: () => import('./modules/user/AccountPage'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
// 个人主页
|
||||
const UsersInfo = Loadable({
|
||||
loader: () => import('./modules/user/usersInfo/Infos'),
|
||||
loading: Loading,
|
||||
})
|
||||
const InfosIndex = Loadable({
|
||||
loader: () => import('./modules/user/usersInfo/InfosIndex'),
|
||||
loading: Loading,
|
||||
})
|
||||
// 题库
|
||||
const BanksIndex = Loadable({
|
||||
loader: () => import('./modules/user/usersInfo/banks/BanksIndex'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
|
||||
// 教学案例
|
||||
const MoopCases = Loadable({
|
||||
loader: () => import('./modules/moop_cases/index'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
// 兴趣页面
|
||||
const Interestpage = Loadable({
|
||||
loader: () => import('./modules/login/EducoderInteresse'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
//众包创新
|
||||
// const ProjectPackages=Loadable({
|
||||
// loader: () => import('./modules/projectPackages/ProjectPackageIndex'),
|
||||
// loading: Loading,
|
||||
// })
|
||||
|
||||
//竞赛
|
||||
const NewCompetitions=Loadable({
|
||||
loader: () => import('./modules/competitions/Competitions'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
//黑客松定制竞赛
|
||||
const Osshackathon=Loadable({
|
||||
loader: () => import('./modules/osshackathon/Osshackathon'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
const Messagerouting= Loadable({
|
||||
loader: () => import('./modules/message/js/Messagerouting'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
const Topicbank= Loadable({
|
||||
loader: () => import('./modules/topic_bank/Topic_bank'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
const Help = Loadable({
|
||||
loader: () => import('./modules/help/Help'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
const Ecs = Loadable({
|
||||
loader: () => import('./modules/ecs/Ecs'),
|
||||
loading: Loading,
|
||||
})
|
||||
|
||||
// 添加开发者社区
|
||||
const Developer = Loadable({
|
||||
loader: () => import('./modules/developer'),
|
||||
loading: Loading
|
||||
})
|
||||
// 试题库
|
||||
const Headplugselection = Loadable({
|
||||
loader: () => import('./modules/question/Question'),
|
||||
loading: Loading
|
||||
})
|
||||
|
||||
//试题库新建 //题库编辑
|
||||
const Questionitem_banks = Loadable({
|
||||
loader: () => import('./modules/question/Questionitem_banks'),
|
||||
loading: Loading
|
||||
})
|
||||
|
||||
//试卷库
|
||||
const Testpaperlibrary= Loadable({
|
||||
loader: () => import('./modules/testpaper/Testpaperlibrary'),
|
||||
loading: Loading
|
||||
})
|
||||
//试卷编辑
|
||||
const Paperlibraryeditid= Loadable({
|
||||
loader: () => import('./modules/testpaper/Paperlibraryeditid'),
|
||||
loading: Loading
|
||||
})
|
||||
//试卷查看
|
||||
const Paperlibraryseeid= Loadable({
|
||||
loader: () => import('./modules/testpaper/Paperlibraryseeid'),
|
||||
loading: Loading
|
||||
})
|
||||
//人工组卷
|
||||
const Paperreview= Loadable({
|
||||
loader: () => import('./modules/question/Paperreview'),
|
||||
loading: Loading
|
||||
})
|
||||
|
||||
//智能组卷
|
||||
const Integeneration= Loadable({
|
||||
loader: () => import('./modules/testpaper/Intecomponents'),
|
||||
loading: Loading
|
||||
})
|
||||
|
||||
// 学院统计
|
||||
const College = Loadable({
|
||||
loader: () => import('./college/College'),
|
||||
loading: Loading
|
||||
})
|
||||
|
||||
// 开发者编辑模块
|
||||
const NewOrEditTask = Loadable({
|
||||
loader: () => import('./modules/developer/newOrEditTask'),
|
||||
loading: Loading
|
||||
});
|
||||
// 学员学习
|
||||
const StudentStudy = Loadable({
|
||||
loader: () => import('./modules/developer/studentStudy'),
|
||||
loading: Loading
|
||||
});
|
||||
// 提交记录详情
|
||||
const RecordDetail = Loadable({
|
||||
loader: () => import('./modules/developer/recordDetail'),
|
||||
loading: Loading
|
||||
});
|
||||
// jupyter tpi
|
||||
const JupyterTPI = Loadable({
|
||||
loader: () => import('./modules/tpm/jupyter'),
|
||||
loading: Loading
|
||||
});
|
||||
// 微信代码编辑器
|
||||
// const WXCode = Loadable({
|
||||
// loader: () => import('./modules/wxcode'),
|
||||
// loading: Loading
|
||||
// });
|
||||
// //个人竞赛报名
|
||||
// const PersonalCompetit = Loadable({
|
||||
// loader: () => import('./modules/competition/personal/PersonalCompetit.js'),
|
||||
// loading: Loading,
|
||||
// });
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
Addcoursestype:false,
|
||||
Addcoursestypes:false,
|
||||
mydisplay:false,
|
||||
occupation:0,
|
||||
mygetHelmetapi: null,
|
||||
}
|
||||
|
||||
}
|
||||
HideAddcoursestypess=(i)=>{
|
||||
console.log("调用了");
|
||||
this.setState({
|
||||
Addcoursestype:false,
|
||||
Addcoursestypes:false,
|
||||
mydisplay:true,
|
||||
occupation:i,
|
||||
})
|
||||
};
|
||||
hideAddcoursestypes=()=>{
|
||||
this.setState({
|
||||
Addcoursestypes:false
|
||||
})
|
||||
};
|
||||
ModalCancelsy=()=>{
|
||||
this.setState({
|
||||
mydisplay:false,
|
||||
})
|
||||
window.location.href = "/";
|
||||
};
|
||||
ModalshowCancelsy=()=>{
|
||||
this.setState({
|
||||
mydisplay:true,
|
||||
})
|
||||
};
|
||||
|
||||
disableVideoContextMenu = () => {
|
||||
window.$( "body" ).on( "mousedown", "video", function(event) {
|
||||
if(event.which === 3) {
|
||||
window.$('video').bind('contextmenu',function () { return false; });
|
||||
} else {
|
||||
window.$('video').unbind('contextmenu');
|
||||
}
|
||||
});
|
||||
}
|
||||
componentDidMount() {
|
||||
document.title = "loading...";
|
||||
this.disableVideoContextMenu();
|
||||
// force an update if the URL changes
|
||||
history.listen(() => {
|
||||
this.forceUpdate()
|
||||
const $ = window.$
|
||||
// https://www.trustie.net/issues/21919 可能会有问题
|
||||
$("html").animate({ scrollTop: $('html').scrollTop() - 0 })
|
||||
});
|
||||
|
||||
initAxiosInterceptors(this.props);
|
||||
// 顶部和底部的动态设置
|
||||
// this.getAppdata();
|
||||
//
|
||||
// axios.interceptors.response.use((response) => {
|
||||
// // console.log("response"+response);
|
||||
// if(response!=undefined)
|
||||
// // console.log("response"+response.data.statu);
|
||||
// if (response&&response.data.status === 407) {
|
||||
// this.setState({
|
||||
// isRenders: true,
|
||||
// })
|
||||
// }
|
||||
// return response;
|
||||
// }, (error) => {
|
||||
// //TODO 这里如果样式变了会出现css不加载的情况
|
||||
// });
|
||||
|
||||
window.addEventListener('error', (event) => {
|
||||
const msg = `${event.type}: ${event.message}`;
|
||||
console.log(msg)
|
||||
});
|
||||
}
|
||||
//修改登录方法
|
||||
Modifyloginvalue=()=>{
|
||||
this.setState({
|
||||
isRender:false,
|
||||
})
|
||||
};
|
||||
|
||||
//获取数据为空的时候
|
||||
gettablogourlnull = () => {
|
||||
this.setState({
|
||||
mygetHelmetapi: undefined
|
||||
});
|
||||
document.title = "EduCoder";
|
||||
var link = document.createElement('link'),
|
||||
oldLink = document.getElementById('dynamic-favicon');
|
||||
link.id = 'dynamic-favicon';
|
||||
link.rel = 'shortcut icon';
|
||||
link.href = "/react/build/./favicon.ico";
|
||||
if (oldLink) {
|
||||
document.head.removeChild(oldLink);
|
||||
}
|
||||
document.head.appendChild(link);
|
||||
};
|
||||
|
||||
//获取数据的时候
|
||||
gettablogourldata = (response) => {
|
||||
document.title = response.data.setting.name;
|
||||
var link = document.createElement('link'),
|
||||
oldLink = document.getElementById('dynamic-favicon');
|
||||
link.id = 'dynamic-favicon';
|
||||
link.rel = 'shortcut icon';
|
||||
link.href = '/' + response.data.setting.tab_logo_url;
|
||||
if (oldLink) {
|
||||
document.head.removeChild(oldLink);
|
||||
}
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
//获取当前定制信息
|
||||
getAppdata=()=>{
|
||||
let url = "/setting.json";
|
||||
axios.get(url).then((response) => {
|
||||
// console.log("app.js开始请求/setting.json");
|
||||
// console.log("获取当前定制信息");
|
||||
if(response){
|
||||
if(response.data){
|
||||
this.setState({
|
||||
mygetHelmetapi:response.data.setting
|
||||
});
|
||||
//存储配置到游览器
|
||||
localStorage.setItem('chromesetting',JSON.stringify(response.data.setting));
|
||||
localStorage.setItem('chromesettingresponse',JSON.stringify(response));
|
||||
try {
|
||||
if (response.data.setting.tab_logo_url) {
|
||||
this.gettablogourldata(response);
|
||||
} else {
|
||||
this.gettablogourlnull();
|
||||
}
|
||||
} catch (e) {
|
||||
this.gettablogourlnull();
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
this.gettablogourlnull();
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
this.gettablogourlnull();
|
||||
|
||||
}
|
||||
|
||||
}).catch((error) => {
|
||||
this.gettablogourlnull();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<ConfigProvider locale={zhCN}>
|
||||
<MuiThemeProvider theme={theme}>
|
||||
<Accountnewprofile {...this.props}{...this.state}/>
|
||||
<LoginDialog {...this.props} {...this.state} Modifyloginvalue={()=>this.Modifyloginvalue()}></LoginDialog>
|
||||
<Notcompletedysl {...this.props} {...this.state}></Notcompletedysl>
|
||||
<Trialapplicationysl {...this.props} {...this.state}></Trialapplicationysl>
|
||||
<Trialapplicationreview {...this.props} {...this.state}></Trialapplicationreview>
|
||||
<Addcourses {...this.props} {...this.state} HideAddcoursestypess={(i)=>this.HideAddcoursestypess(i)}/>
|
||||
<AccountProfile {...this.props} {...this.state} />
|
||||
<Certifiedprofessional {...this.props} {...this.state} ModalCancelsy={this.ModalCancelsy} ModalshowCancelsy={this.ModalshowCancelsy}/>
|
||||
<Router>
|
||||
<Switch>
|
||||
|
||||
{/* 项目 */}
|
||||
<Route path="/projects"
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<ProjectIndex {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}></Route>
|
||||
|
||||
<Route exact path="/"
|
||||
// component={ShixunsHome}
|
||||
render={
|
||||
(props)=>(<ProjectIndex {...this.props} {...props} {...this.state}></ProjectIndex>)
|
||||
}
|
||||
/>
|
||||
{/*题库*/}
|
||||
<Route path="/topicbank/:username/:topicstype"
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<Topicbank {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}></Route>
|
||||
{/*题库*/}
|
||||
<Route path="/topicbank/:topicstype"
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<Topicbank {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}></Route>
|
||||
{/*/!*众包创新*!/*/}
|
||||
{/*<Route path={"/crowdsourcing"} component={ProjectPackages}/>*/}
|
||||
{/*竞赛*/}
|
||||
<Route path={"/competitions"}
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<NewCompetitions {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}></Route>
|
||||
|
||||
{/*黑客松定制竞赛*/}
|
||||
<Route
|
||||
path={"/osshackathon"}
|
||||
render={
|
||||
(props)=>{
|
||||
return(
|
||||
<Osshackathon {...this.props} {...props} {...this.state} />
|
||||
)
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
{/*认证*/}
|
||||
<Route path="/account" component={AccountPage}/>
|
||||
|
||||
{/*403*/}
|
||||
<Route path="/403" component={Shixunauthority}/>
|
||||
|
||||
<Route path="/500" component={http500}/>
|
||||
|
||||
{/*404*/}
|
||||
<Route path="/nopage" component={Shixunnopage}/>
|
||||
|
||||
<Route path="/compatibility" component={CompatibilityPageLoadable}/>
|
||||
<Route
|
||||
path="/login"
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<EducoderLogin {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/register"
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<EducoderLogin {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/otherloginstart" component={Otherloginstart}
|
||||
/>
|
||||
<Route
|
||||
path={"/otherloginqq"} component={Otherloginsqq}
|
||||
/>
|
||||
<Route
|
||||
path="/otherlogin" component={Otherlogin}
|
||||
/>
|
||||
<Route
|
||||
path="/loginqq" component={Loginqq}
|
||||
/>
|
||||
|
||||
<Route path="/users/:username"
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<InfosIndex {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}></Route>
|
||||
|
||||
<Route path="/banks"
|
||||
render={
|
||||
(props) => {
|
||||
return (<BanksIndex {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}></Route>
|
||||
{/*<Route*/}
|
||||
{/*path="/personalcompetit"*/}
|
||||
{/*render={*/}
|
||||
{/*(props) => (<PersonalCompetit {...this.props} {...props} {...this.state}></PersonalCompetit>)*/}
|
||||
{/*}*/}
|
||||
{/*/>*/}
|
||||
<Route
|
||||
path="/changepassword"
|
||||
render={
|
||||
(props) => {
|
||||
|
||||
return (<EducoderLogin {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
}
|
||||
/>
|
||||
{/*<Route*/}
|
||||
{/* path="/interesse" component={Interestpage}*/}
|
||||
|
||||
{/*/>*/}
|
||||
<Route path="/shixuns/new" component={Newshixuns}>
|
||||
</Route>
|
||||
<Route path="/colleges/:id/statistics"
|
||||
render={
|
||||
(props) => (<College {...this.props} {...props} {...this.state} />)
|
||||
}/>
|
||||
{/* jupyter */}
|
||||
<Route path="/tasks/:identifier/jupyter/"
|
||||
render={
|
||||
(props) => {
|
||||
return (<JupyterTPI {...this.props} {...props} {...this.state}/>)
|
||||
}
|
||||
}
|
||||
/>
|
||||
|
||||
<Route path="/myproblems/record_detail/:id"
|
||||
render={
|
||||
(props) => (<RecordDetail {...this.props} {...props} {...this.state} />)
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/problems/:id/edit"
|
||||
render={
|
||||
(props) => (<NewOrEditTask {...this.props} {...props} {...this.state} />)
|
||||
} />
|
||||
<Route path="/Integeneration/:type/:id"
|
||||
render={
|
||||
(props) => (<Paperreview {...this.props} {...props} {...this.state} />)
|
||||
}/>
|
||||
<Route path="/paperreview/:type"
|
||||
render={
|
||||
(props) => (<Paperreview {...this.props} {...props} {...this.state} />)
|
||||
}/>
|
||||
<Route path="/paperlibrary/edit/:id"
|
||||
render={
|
||||
(props) => (<Paperlibraryeditid {...this.props} {...props} {...this.state} />)
|
||||
}/>
|
||||
|
||||
<Route path="/paperlibrary/see/:id"
|
||||
render={
|
||||
(props) => (<Paperlibraryseeid {...this.props} {...props} {...this.state} />)
|
||||
}/>
|
||||
|
||||
<Route path="/myproblems/:id/:tab?"
|
||||
render={
|
||||
(props) => (<StudentStudy {...this.props} {...props} {...this.state} />)
|
||||
} />
|
||||
<Route path="/question/edit/:id"
|
||||
render={
|
||||
(props) => (<Questionitem_banks {...this.props} {...props} {...this.state} />)
|
||||
} />
|
||||
|
||||
<Route path="/question/newitem"
|
||||
render={
|
||||
(props) => (<Questionitem_banks {...this.props} {...props} {...this.state} />)
|
||||
} />
|
||||
<Route path="/question/:type"
|
||||
render={
|
||||
(props) => (<Headplugselection {...this.props} {...props} {...this.state} />)
|
||||
} />
|
||||
<Route path="/paperlibrary"
|
||||
render={
|
||||
(props) => (<Testpaperlibrary {...this.props} {...props} {...this.state} />)
|
||||
}/>
|
||||
|
||||
<Route path="/Integeneration"
|
||||
render={
|
||||
(props) => (<Integeneration {...this.props} {...props} {...this.state} />)
|
||||
}/>
|
||||
|
||||
<Route path="/problems"
|
||||
render={
|
||||
(props) => (<Developer {...this.props} {...props} {...this.state} />)
|
||||
}/>
|
||||
|
||||
<Route path="/question"
|
||||
render={
|
||||
(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="/"
|
||||
// component={ShixunsHome}
|
||||
render={
|
||||
(props)=>(<ShixunsHome {...this.props} {...props} {...this.state}></ShixunsHome>)
|
||||
}
|
||||
/>
|
||||
<Route component={Shixunnopage}/>
|
||||
|
||||
</Switch>
|
||||
</Router>
|
||||
</MuiThemeProvider>
|
||||
</ConfigProvider>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// moment国际化,设置为中文
|
||||
moment.defineLocale('zh-cn', {
|
||||
months: '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'),
|
||||
monthsShort: '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'),
|
||||
weekdays: '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'),
|
||||
weekdaysShort: '周日_周一_周二_周三_周四_周五_周六'.split('_'),
|
||||
weekdaysMin: '日_一_二_三_四_五_六'.split('_'),
|
||||
longDateFormat: {
|
||||
LT: 'Ah点mm分',
|
||||
LTS: 'Ah点m分s秒',
|
||||
L: 'YYYY-MM-DD',
|
||||
LL: 'YYYY年MMMD日',
|
||||
LLL: 'YYYY年MMMD日Ah点mm分',
|
||||
LLLL: 'YYYY年MMMD日ddddAh点mm分',
|
||||
l: 'YYYY-MM-DD',
|
||||
ll: 'YYYY年MMMD日',
|
||||
lll: 'YYYY年MMMD日Ah点mm分',
|
||||
llll: 'YYYY年MMMD日ddddAh点mm分'
|
||||
},
|
||||
meridiemParse: /凌晨|早上|上午|中午|下午|晚上/,
|
||||
meridiemHour: function (hour, meridiem) {
|
||||
if (hour === 12) {
|
||||
hour = 0;
|
||||
}
|
||||
if (meridiem === '凌晨' || meridiem === '早上' ||
|
||||
meridiem === '上午') {
|
||||
return hour;
|
||||
} else if (meridiem === '下午' || meridiem === '晚上') {
|
||||
return hour + 12;
|
||||
} else {
|
||||
// '中午'
|
||||
return hour >= 11 ? hour : hour + 12;
|
||||
}
|
||||
},
|
||||
meridiem: function (hour, minute, isLower) {
|
||||
var hm = hour * 100 + minute;
|
||||
if (hm < 600) {
|
||||
return '凌晨';
|
||||
} else if (hm < 900) {
|
||||
return '早上';
|
||||
} else if (hm < 1130) {
|
||||
return '上午';
|
||||
} else if (hm < 1230) {
|
||||
return '中午';
|
||||
} else if (hm < 1800) {
|
||||
return '下午';
|
||||
} else {
|
||||
return '晚上';
|
||||
}
|
||||
},
|
||||
calendar: {
|
||||
sameDay: function () {
|
||||
return this.minutes() === 0 ? '[今天]Ah[点整]' : '[今天]LT';
|
||||
},
|
||||
nextDay: function () {
|
||||
return this.minutes() === 0 ? '[明天]Ah[点整]' : '[明天]LT';
|
||||
},
|
||||
lastDay: function () {
|
||||
return this.minutes() === 0 ? '[昨天]Ah[点整]' : '[昨天]LT';
|
||||
},
|
||||
nextWeek: function () {
|
||||
var startOfWeek, prefix;
|
||||
startOfWeek = moment().startOf('week');
|
||||
prefix = this.unix() - startOfWeek.unix() >= 7 * 24 * 3600 ? '[下]' : '[本]';
|
||||
return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
|
||||
},
|
||||
lastWeek: function () {
|
||||
var startOfWeek, prefix;
|
||||
startOfWeek = moment().startOf('week');
|
||||
prefix = this.unix() < startOfWeek.unix() ? '[上]' : '[本]';
|
||||
return this.minutes() === 0 ? prefix + 'dddAh点整' : prefix + 'dddAh点mm';
|
||||
},
|
||||
sameElse: 'LL'
|
||||
},
|
||||
ordinalParse: /\d{1,2}(日|月|周)/,
|
||||
ordinal: function (number, period) {
|
||||
switch (period) {
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'DDD':
|
||||
return number + '日';
|
||||
case 'M':
|
||||
return number + '月';
|
||||
case 'w':
|
||||
case 'W':
|
||||
return number + '周';
|
||||
default:
|
||||
return number;
|
||||
}
|
||||
},
|
||||
relativeTime: {
|
||||
future: '%s内',
|
||||
past: '%s前',
|
||||
s: '几秒',
|
||||
m: '1分钟',
|
||||
mm: '%d分钟',
|
||||
h: '1小时',
|
||||
hh: '%d小时',
|
||||
d: '1天',
|
||||
dd: '%d天',
|
||||
M: '1个月',
|
||||
MM: '%d个月',
|
||||
y: '1年',
|
||||
yy: '%d年'
|
||||
},
|
||||
week: {
|
||||
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4 // The week that contains Jan 4th is the first week of the year.
|
||||
}
|
||||
});
|
||||
export default SnackbarHOC()(App) ;
|
||||
9
public/react/src/App.test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
420
public/react/src/AppConfig.js
Normal file
@@ -0,0 +1,420 @@
|
||||
import React from "react";
|
||||
|
||||
import axios from 'axios';
|
||||
import md5 from 'md5';
|
||||
import { requestProxy } from "./indexEduplus2RequestProxy";
|
||||
import { broadcastChannelOnmessage ,SetAppModel, isDev, queryString } from 'educoder';
|
||||
import { notification } from 'antd';
|
||||
import cookie from 'react-cookies';
|
||||
import './index.css';
|
||||
const $ = window.$;
|
||||
const opens ="79e33abd4b6588941ab7622aed1e67e8";
|
||||
let timestamp;
|
||||
let checkSubmitFlg = false;
|
||||
let message501=false;
|
||||
|
||||
broadcastChannelOnmessage('refreshPage', () => {
|
||||
window.location.reload()
|
||||
})
|
||||
|
||||
function locationurl(list){
|
||||
if (window.location.port === "3007") {
|
||||
|
||||
} else {
|
||||
window.location.href=list
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// TODO 开发期多个身份切换
|
||||
let debugType =""
|
||||
if (isDev) {
|
||||
const _search = window.location.search;
|
||||
let parsed = {};
|
||||
if (_search) {
|
||||
parsed = queryString.parse(_search);
|
||||
}
|
||||
debugType = window.location.search.indexOf('debug=t') != -1 ? 'teacher' :
|
||||
window.location.search.indexOf('debug=s') != -1 ? 'student' :
|
||||
window.location.search.indexOf('debug=a') != -1 ? 'admin' : parsed.debug || 'admin'
|
||||
}
|
||||
// 超管
|
||||
// debugType="admin";
|
||||
// 老师
|
||||
//debugType="teacher";
|
||||
// 学生
|
||||
//debugType="student";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function clearAllCookie() {
|
||||
cookie.remove('_educoder_session', {path: '/'});
|
||||
cookie.remove('autologin_trustie', {path: '/'});
|
||||
setpostcookie()
|
||||
}
|
||||
clearAllCookie();
|
||||
function setpostcookie() {
|
||||
|
||||
const str =window.location.pathname;
|
||||
// console.log(str.indexOf("/wxcode"))
|
||||
let newdomain=".educoder.net"
|
||||
if(str.indexOf("/wxcode") !== -1){
|
||||
console.log("123")
|
||||
cookie.remove('_educoder_session', {path: '/'});
|
||||
cookie.remove('autologin_trustie', {path: '/'});
|
||||
// console.log("开始重写cookis");
|
||||
const _params = window.location.search;
|
||||
// console.log("1111");
|
||||
if (_params) {
|
||||
// console.log("22222");
|
||||
let _search = _params.split('?')[1];
|
||||
let _educoder_sessions= _search.split('&')[0].split('=');
|
||||
cookie.save('_educoder_session',_educoder_sessions[1], { domain:'.educoder.net', path: '/'});
|
||||
let autologin_trusties=_search.split('&')[1].split('=');
|
||||
cookie.save('autologin_trustie',autologin_trusties[1], { domain:'.educoder.net', path: '/'});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
setpostcookie();
|
||||
|
||||
|
||||
function railsgettimes(proxy) {
|
||||
|
||||
clearAllCookie()
|
||||
|
||||
if(timestamp&&checkSubmitFlg===false){
|
||||
$.ajax({url:proxy,async:false,success:function(data){
|
||||
if(data.status===0){
|
||||
timestamp=data.message;
|
||||
setpostcookie();
|
||||
}
|
||||
}})
|
||||
checkSubmitFlg=true
|
||||
window.setTimeout(()=>{
|
||||
checkSubmitFlg=false;
|
||||
}, 2000);
|
||||
}else if(checkSubmitFlg===false){
|
||||
$.ajax({url:proxy,async:false,success:function(data){
|
||||
if(data.status===0){
|
||||
timestamp=data.message;
|
||||
setpostcookie();
|
||||
}
|
||||
}})
|
||||
checkSubmitFlg=true
|
||||
window.setTimeout( ()=>{
|
||||
checkSubmitFlg=false;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
window._debugType = debugType;
|
||||
export function initAxiosInterceptors(props) {
|
||||
initOnlineOfflineListener()
|
||||
|
||||
// TODO 避免重复的请求 https://github.com/axios/axios#cancellation
|
||||
// https://github.com/axios/axios/issues/1497
|
||||
|
||||
// TODO 读取到package.json中的配置?
|
||||
var proxy = "http://localhost:3000"
|
||||
// proxy = "http://testbdweb.trustie.net"
|
||||
// proxy = "http://testbdweb.educoder.net"
|
||||
// proxy = "https://testeduplus2.educoder.net"
|
||||
//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-newweb.educoder.net"
|
||||
// proxy="https://test-jupyterweb.educoder.net"
|
||||
//proxy="http://192.168.2.63:3001"
|
||||
|
||||
var //proxy = "http://localhost:3000"
|
||||
// proxy="http://123.59.135.93:56666"
|
||||
proxy="http://localhost:3000"
|
||||
|
||||
// 在这里使用requestMap控制,避免用户通过双击等操作发出重复的请求;
|
||||
// 如果需要支持重复的请求,考虑config里面自定义一个allowRepeat参考来控制
|
||||
const requestMap = {};
|
||||
|
||||
window.setfalseInRequestMap = function(keyName) {
|
||||
requestMap[keyName] = false;
|
||||
}
|
||||
|
||||
//响应前的设置
|
||||
axios.interceptors.request.use(
|
||||
config => {
|
||||
setpostcookie()
|
||||
clearAllCookie()
|
||||
// config.headers['Content-Type']= 'no-cache'
|
||||
// if (token) { // 每次发送请求之前判断是否存在token,如果存在,则统一在http请求的header都加上token,不用每次请求都手动添加了
|
||||
// config.headers.Authorization = token;
|
||||
// }
|
||||
|
||||
// --------------------------------------------- 測試3007连测试服的代码
|
||||
// if (url.indexOf('file_update') != -1 || url.indexOf('game_build') != -1 || url.indexOf('game_status') != -1) {
|
||||
// proxy = 'https://testbdweb.trustie.net'
|
||||
// } else {
|
||||
// proxy = 'http://localhost:3000'
|
||||
// }
|
||||
// ---------------------------------------------
|
||||
// console.log("开始请求了");
|
||||
// console.log(config.url);
|
||||
// console.log(window.location.pathname);
|
||||
//
|
||||
|
||||
// try {
|
||||
// const str =window.location.pathname;
|
||||
// if(str.indexOf("/wxcode") !== -1){
|
||||
// // console.log("开始重写cookis");
|
||||
// const _params = window.location.search;
|
||||
// // console.log("1111");
|
||||
// if (_params) {
|
||||
// // console.log("22222");
|
||||
// let _search = _params.split('?')[1];
|
||||
// var _educoder_sessionmys="";
|
||||
// var autologin_trusties="";
|
||||
// _search.split('&').forEach(item => {
|
||||
// const _arr = item.split('=');
|
||||
// if(_arr[0]==='_educoder_session'){
|
||||
// cookie.save('_educoder_session',_arr[1], { domain: '.educoder.net', path: '/'});
|
||||
// _educoder_sessionmys=_arr[1];
|
||||
// }else{
|
||||
// cookie.save('autologin_trustie',_arr[1], { domain: '.educoder.net', path: '/'});
|
||||
// autologin_trusties=_arr[1];
|
||||
// }
|
||||
// });
|
||||
// try {
|
||||
// const autlogins= `_educoder_session=${_educoder_sessionmys}; autologin_trustie=${autologin_trusties} `;
|
||||
// config.params = {'Cookie': autlogins}
|
||||
// config.headers['Cookie'] =autlogins;
|
||||
// // console.log("设置了cookis");
|
||||
// } catch (e) {
|
||||
//
|
||||
// }
|
||||
// try {
|
||||
// const autloginysls= `_educoder_session=${_educoder_sessionmys}; autologin_trustie=${autologin_trusties} `;
|
||||
// config.params = {'autloginysls': autloginysls}
|
||||
// config.headers['Cookie'] =autloginysls;
|
||||
// // console.log("设置了cookis");
|
||||
// }catch (e) {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }catch (e) {
|
||||
//
|
||||
// }
|
||||
|
||||
|
||||
|
||||
if (config.url.indexOf(proxy) != -1 || config.url.indexOf(':') != -1) {
|
||||
return config
|
||||
}
|
||||
requestProxy(config)
|
||||
|
||||
let url = `/api${config.url}`;
|
||||
|
||||
//qq登录去掉api
|
||||
if(config.params&&config.params.redirect_uri!=undefined){
|
||||
if(config.params.redirect_uri.indexOf('otherloginqq')!=-1){
|
||||
url = `${config.url}`;
|
||||
}
|
||||
}
|
||||
if(`${config[0]}`!=`true`){
|
||||
let timestamp = Date.parse(new Date())/1000;
|
||||
if (window.location.port === "3007") {
|
||||
// let timestamp=railsgettimes(proxy);
|
||||
// console.log(timestamp)
|
||||
// `https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp`
|
||||
railsgettimes( `${proxy}/api/main/first_stamp.json`);
|
||||
let newopens=md5(opens+timestamp)
|
||||
config.url = `${proxy}${url}`;
|
||||
if (config.url.indexOf('?') == -1) {
|
||||
config.url = `${config.url}?debug=${debugType}&randomcode=${timestamp}&client_key=${newopens}`;
|
||||
} else {
|
||||
config.url = `${config.url}&debug=${debugType}&randomcode=${timestamp}&client_key=${newopens}`;
|
||||
}
|
||||
} else {
|
||||
// 加api前缀
|
||||
// railsgettimes(`http://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp`);
|
||||
|
||||
railsgettimes( `/api/main/first_stamp.json`);
|
||||
let newopens=md5(opens+timestamp)
|
||||
config.url = url;
|
||||
if (config.url.indexOf('?') == -1) {
|
||||
config.url = `${config.url}?randomcode=${timestamp}&client_key=${newopens}`;
|
||||
} else {
|
||||
config.url = `${config.url}&randomcode=${timestamp}&client_key=${newopens}`;
|
||||
}
|
||||
}
|
||||
setpostcookie();
|
||||
}
|
||||
//
|
||||
// console.log(config);
|
||||
if (config.method === "post") {
|
||||
if (requestMap[config.url] === true) { // 避免重复的请求 导致页面f5刷新 也会被阻止 显示这个方法会影响到定制信息
|
||||
// console.log(config);
|
||||
// console.log(JSON.parse(config));
|
||||
// console.log(config.url);
|
||||
// console.log("被阻止了是重复请求=================================");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 非file_update请求
|
||||
if (config.url.indexOf('update_file') === -1) {
|
||||
requestMap[config.url] = true;
|
||||
|
||||
window.setTimeout("setfalseInRequestMap('"+config.url+"')", 900)
|
||||
}
|
||||
// setTimeout("setfalseInRequestMap(" + config.url + ")", 1200)
|
||||
return config;
|
||||
},
|
||||
err => {
|
||||
return Promise.reject(err);
|
||||
});
|
||||
|
||||
axios.interceptors.response.use(function (response) {
|
||||
|
||||
// console.log(".............")
|
||||
if(response===undefined){
|
||||
return
|
||||
}
|
||||
const config = response.config
|
||||
if (response.data.status === -1) {
|
||||
// console.error('error:', response.data.message)
|
||||
// throw new Error()
|
||||
|
||||
// https://github.com/axios/axios/issues?utf8=%E2%9C%93&q=cancel+request+in+response+interceptors+
|
||||
// https://github.com/axios/axios/issues/583
|
||||
// message.info(response.data.message || '服务端返回status -1,请联系管理员。');
|
||||
// props.showSnackbar( response.data.message || '服务器异常,请联系管理员。' )
|
||||
if (window.location.pathname.startsWith('/tasks/')) {
|
||||
props.showSnackbar( response.data.message || '服务器异常,请联系管理员。' )
|
||||
} else {
|
||||
notification.open({
|
||||
message:"提示",
|
||||
description: response.data.message || '服务器异常,请联系管理员。',
|
||||
style: {
|
||||
zIndex: 99999999
|
||||
},
|
||||
});
|
||||
// notification['error']({
|
||||
// message:"提示",
|
||||
// description: response.data.message || '服务器异常,请联系管理员。',
|
||||
// });
|
||||
}
|
||||
|
||||
throw new axios.Cancel('Operation canceled by the user.');
|
||||
} else {
|
||||
// hash跳转
|
||||
// var hash = window.location.hash;
|
||||
// if (hash) {
|
||||
// hashTimeout && window.clearTimeout(hashTimeout)
|
||||
// hashTimeout = setTimeout(() => {
|
||||
// var element = document.querySelector(hash);
|
||||
// if (element) {
|
||||
// element.scrollIntoView();
|
||||
// }
|
||||
// }, 400)
|
||||
// }
|
||||
}
|
||||
// if(response.data.status === 401){
|
||||
// console.log("401401401")
|
||||
// }
|
||||
if (response.data.status === 403||response.data.status === "403") {
|
||||
|
||||
locationurl('/403');
|
||||
}
|
||||
|
||||
if (response.data.status === 404) {
|
||||
locationurl('/nopage');
|
||||
}
|
||||
|
||||
if (response.data.status === 500) {
|
||||
locationurl('/500');
|
||||
}
|
||||
|
||||
if (response.data.status === 501) {
|
||||
if(message501===false){
|
||||
message501=true
|
||||
notification.open({
|
||||
message:"提示",
|
||||
description:response.data.message || '访问异常,请求不合理',
|
||||
style: {
|
||||
zIndex: 99999999
|
||||
}
|
||||
})
|
||||
}
|
||||
window.setTimeout(function () {
|
||||
message501=false
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
|
||||
// if (response.data.status === 402) {
|
||||
// console.log(response.data.status);
|
||||
// console.log(response.data);
|
||||
// // locationurl(402);
|
||||
// }
|
||||
|
||||
//
|
||||
// if (response.data.status === 401) {
|
||||
// console.log("161");
|
||||
// console.log(config);
|
||||
// return config;
|
||||
// }
|
||||
// if (response.data.status === 407) {
|
||||
// 在app js 中解决 Trialapplication
|
||||
// // </Trialapplication>
|
||||
// ///在appjs
|
||||
// notification.open({
|
||||
// message:"提示",
|
||||
// description: "账号未认证",
|
||||
// });
|
||||
// throw new axios.Cancel('Operation canceled by the user.');
|
||||
// //
|
||||
// }
|
||||
|
||||
requestMap[response.config.url] = false;
|
||||
setpostcookie();
|
||||
return response;
|
||||
}, function (error) {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
// -----------------------------------------------------------------------------------
|
||||
|
||||
}
|
||||
|
||||
|
||||
function initOnlineOfflineListener() {
|
||||
const $ = window.$
|
||||
$(window).bind("online", () => {
|
||||
notification.destroy()
|
||||
notification.success({
|
||||
duration: 2,
|
||||
message: '网络恢复正常',
|
||||
description:
|
||||
'网络恢复正常,感谢使用。',
|
||||
})
|
||||
});
|
||||
$(window).bind("offline", () => {
|
||||
notification.destroy()
|
||||
|
||||
notification.warning({
|
||||
duration: null,
|
||||
message: '网络异常',
|
||||
description:
|
||||
'网络异常,请检测网络后重试。',
|
||||
})
|
||||
});
|
||||
}
|
||||
12
public/react/src/CustomLoadable.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import Loadable from 'react-loadable';
|
||||
|
||||
import Loading from "./Loading";
|
||||
|
||||
const CustomLoadable = (loader, loading = Loading) => {
|
||||
return Loadable({
|
||||
loader,
|
||||
loading
|
||||
})
|
||||
}
|
||||
|
||||
export default CustomLoadable
|
||||
34
public/react/src/Loading.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
|
||||
|
||||
import { Spin } from 'antd';
|
||||
|
||||
class Loading extends Component {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevProps.error && this.props.error) {
|
||||
console.log(this.props.error)
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// Loading
|
||||
return (
|
||||
<div className="App" style={{minHeight: '800px',width:"100%"}}>
|
||||
<style>
|
||||
{
|
||||
`
|
||||
.margintop{
|
||||
margin-top:20%;
|
||||
}
|
||||
`
|
||||
}
|
||||
</style>
|
||||
<Spin size="large" className={"margintop"}/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Loading;
|
||||
41
public/react/src/NotFoundPage.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
|
||||
|
||||
class NotFoundPage extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
404 Page
|
||||
<br></br>
|
||||
|
||||
<Link to="/tasks/ixq5euhgrf7y">Index</Link>
|
||||
|
|
||||
<Link to="/shixuns/uznmbg54/challenges">tpm challenges</Link>
|
||||
|
|
||||
<Link to="/shixuns/uznmbg54/shixun_discuss">tpm discuss</Link>
|
||||
|
|
||||
<Link to="/forums/categories/all">forums</Link>
|
||||
|
|
||||
<Link to="/comment">Comment</Link>
|
||||
|
|
||||
<Link to="/testMaterial">testMaterial</Link>
|
||||
|
|
||||
<Link to="/testCodeMirror">testCodeMirror</Link>
|
||||
|
|
||||
<Link to="/taskList">taskList</Link>
|
||||
|
|
||||
<Link to="/testRCComponent">testRCComponent</Link>
|
||||
|
|
||||
<Link to="/tpforums">tpforums</Link>
|
||||
|
||||
|
||||
|
|
||||
<Link to="/testUrlQuery">url-query test</Link>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NotFoundPage;
|
||||
1261
public/react/src/college/College.js
Normal file
84
public/react/src/college/colleagechart/Colleagechart.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, {Component} from "react";
|
||||
import {WordsBtn} from 'educoder';
|
||||
import {Table} from "antd";
|
||||
import {Link,Switch,Route,Redirect} from 'react-router-dom';
|
||||
const echarts = require('echarts');
|
||||
|
||||
|
||||
|
||||
function startechart(data,datanane){
|
||||
var effChart = echarts.init(document.getElementById('shixun_skill_chart'));
|
||||
|
||||
var option = {
|
||||
|
||||
tooltip : {
|
||||
trigger: 'item',
|
||||
formatter: "{d}% <br/>"
|
||||
},
|
||||
legend: {
|
||||
// orient: 'vertical',
|
||||
// top: 'middle',
|
||||
bottom: 50,
|
||||
left: 'center',
|
||||
data: datanane
|
||||
},
|
||||
series : [
|
||||
{
|
||||
type: 'pie',
|
||||
radius : '65%',
|
||||
center: ['50%', '35%'],
|
||||
selectedMode: 'single',
|
||||
data:data,
|
||||
itemStyle: {
|
||||
emphasis: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
effChart.setOption(option);
|
||||
}
|
||||
class Colleagechart extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
startechart(this.props.data,this.props.datanane)
|
||||
}
|
||||
|
||||
|
||||
componentDidUpdate = (prevProps) => {
|
||||
if (prevProps.data!= this.props.data) {
|
||||
startechart(this.props.data,this.props.datanane)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let {data}=this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
<div
|
||||
style={{ width:'100%',height:'600px'}}
|
||||
id="shixun_skill_chart">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Colleagechart;
|
||||
149
public/react/src/college/colleagechart/Colleagechartzu.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import React, {Component} from "react";
|
||||
import {WordsBtn} from 'educoder';
|
||||
import {Table} from "antd";
|
||||
import {Link,Switch,Route,Redirect} from 'react-router-dom';
|
||||
const echarts = require('echarts');
|
||||
|
||||
|
||||
|
||||
function startechart(names, values){
|
||||
var effChart = echarts.init(document.getElementById('shixun_skill_charts'));
|
||||
|
||||
var Color = ['#962e66', '#623363', '#CCCCCC', '#9A9A9A', '#FF8080', '#FF80C2', '#B980FF', '#80B9FF', '#6FE9FF', '#4DE8B4', '#F8EF63', '#FFB967'];
|
||||
|
||||
var option = {
|
||||
backgroundColor: '#fff',
|
||||
grid: {
|
||||
left: '3%',
|
||||
right: '8%',
|
||||
bottom: '15%',
|
||||
containLabel: true
|
||||
},
|
||||
|
||||
tooltip: {
|
||||
show: "true",
|
||||
trigger: 'item',
|
||||
formatter: '{c0}',
|
||||
backgroundColor: 'rgba(0,0,0,0.7)', // 背景
|
||||
padding: [8, 10], //内边距
|
||||
extraCssText: 'box-shadow: 0 0 3px rgba(255, 255, 255, 0.4);', //添加阴影
|
||||
axisPointer: { // 坐标轴指示器,坐标轴触发有效
|
||||
type: 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
||||
}
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#CCCCCC'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: false,
|
||||
lineStyle: {
|
||||
color: '#CCCCCC'
|
||||
}
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#656565',
|
||||
fontWeight: 'normal',
|
||||
fontSize: '12'
|
||||
},
|
||||
formatter: '{value}'
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: '#cccccc'
|
||||
}
|
||||
},
|
||||
splitLine: {
|
||||
show: false
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
splitArea: {
|
||||
show: false
|
||||
},
|
||||
axisLabel: {
|
||||
inside: false,
|
||||
textStyle: {
|
||||
color: '#656565',
|
||||
fontWeight: 'normal',
|
||||
fontSize: '12'
|
||||
}
|
||||
},
|
||||
data: names
|
||||
},
|
||||
series: [{
|
||||
name: '',
|
||||
type: 'bar',
|
||||
itemStyle: {
|
||||
normal: {
|
||||
show: true,
|
||||
color: function(params) {
|
||||
return Color[params.dataIndex]
|
||||
},
|
||||
barBorderRadius: 50,
|
||||
borderWidth: 0,
|
||||
borderColor: '#333'
|
||||
}
|
||||
},
|
||||
barGap: '0%',
|
||||
barCategoryGap: '50%',
|
||||
data: values
|
||||
}
|
||||
|
||||
]
|
||||
};
|
||||
effChart.setOption(option);
|
||||
}
|
||||
class Colleagechartzu extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
startechart(this.props.data,this.props.datavule)
|
||||
}
|
||||
|
||||
|
||||
componentDidUpdate = (prevProps) => {
|
||||
if (prevProps.data!= this.props.data) {
|
||||
startechart(this.props.data,this.props.datavule)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let {data}=this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
<div
|
||||
style={{ width:'100%',height:'600px'}}
|
||||
id="shixun_skill_charts">
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Colleagechartzu;
|
||||
213
public/react/src/college/colleagecss/colleage.css
Normal file
@@ -0,0 +1,213 @@
|
||||
.yslstatistic-header {
|
||||
width: 100%;
|
||||
height: 240px;
|
||||
background-image: url('/images/educoder/statistics.jpg');
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
.yslborder{
|
||||
border: 1px solid;
|
||||
}
|
||||
.yslstatistic-header-title{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #4CACFF;
|
||||
font-size: 32px;
|
||||
}
|
||||
.yslstatistic-header-content{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.yslstatistic-header-item{
|
||||
margin-bottom: 22px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
}
|
||||
.yslstatistic-header-item-label{
|
||||
color: #989898;
|
||||
}
|
||||
|
||||
.yslstatistic-base-item-label{
|
||||
width: 217px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
color: #686868;
|
||||
background: #F5F5F5;
|
||||
border-top: 1px solid #EBEBEB;
|
||||
}
|
||||
.yslstatistic-base-item-labels{
|
||||
width: 217px;
|
||||
text-align: center;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid #EBEBEB;
|
||||
border-bottom: 1px solid #EBEBEB;
|
||||
}
|
||||
.yslstatistic-base-item-labelsp{
|
||||
color: #000000;
|
||||
font-size: 24px;
|
||||
}
|
||||
.yslstatistic-base-item-labelsspan{
|
||||
color: #000000;
|
||||
margin-left: 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.jibenshiyong100{
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.yslstatistic-header-item-content{
|
||||
font-size: 24px;
|
||||
}
|
||||
/* 中间居中 */
|
||||
.intermediatecenter{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* 简单居中 */
|
||||
.intermediatecenterysls{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.spacearound{
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
}
|
||||
.spacebetween{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
/* 头顶部居中 */
|
||||
.topcenter{
|
||||
display: -webkit-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* x轴正方向排序 */
|
||||
/* 一 二 三 四 五 六 七 八 */
|
||||
.sortinxdirection{
|
||||
display: flex;
|
||||
flex-direction:row;
|
||||
}
|
||||
/* x轴反方向排序 */
|
||||
/* 八 七 六 五 四 三 二 一 */
|
||||
.xaxisreverseorder{
|
||||
display: flex;
|
||||
flex-direction:row-reverse;
|
||||
}
|
||||
/* 垂直布局 正方向*/
|
||||
/* 一
|
||||
二
|
||||
三
|
||||
四
|
||||
五
|
||||
六
|
||||
七
|
||||
八 */
|
||||
.verticallayout{
|
||||
display: flex;
|
||||
flex-direction:column;
|
||||
}
|
||||
/* 垂直布局 反方向*/
|
||||
.reversedirection{
|
||||
display: flex;
|
||||
flex-direction:column-reverse;
|
||||
}
|
||||
|
||||
.h4{
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
.ysllinjibenshiyong{
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
padding: 2rem 1.25rem;
|
||||
border-bottom: unset;
|
||||
background:#fff;
|
||||
}
|
||||
.linjibenshiyong{
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
padding: 2rem 1.25rem;
|
||||
border-bottom: unset;
|
||||
background:#fff;
|
||||
box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1);
|
||||
border-radius:2px;
|
||||
}
|
||||
.yslslinjibenshiyong{
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
border-bottom: unset;
|
||||
box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1);
|
||||
border-radius:2px;
|
||||
}
|
||||
.yinyin{
|
||||
background: #fff;
|
||||
box-shadow:0px 6px 12px 0px rgba(0,0,0,0.1);
|
||||
border-radius:2px;
|
||||
}
|
||||
.edu-back-eeee{
|
||||
background:#EEEEEE !important;
|
||||
}
|
||||
.mt-4{
|
||||
margin-top: 1.5rem !important;
|
||||
}
|
||||
|
||||
.statistic-label{
|
||||
padding: 2rem 1.25rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
.mb50{
|
||||
padding-bottom: 50px !important;
|
||||
}
|
||||
.mt40{
|
||||
margin-top: 40px;
|
||||
}
|
||||
.mb80{
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
.task-hide{overflow:hidden; white-space: nowrap; text-overflow:ellipsis;}
|
||||
a:hover{
|
||||
color:#0056b3;
|
||||
}
|
||||
.color-blue{
|
||||
color: #4CACFF;
|
||||
}
|
||||
|
||||
.color-huang{
|
||||
color:#ffc107 !important
|
||||
}
|
||||
.maxnamewidth105{
|
||||
max-width: 105px;
|
||||
overflow:hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space:nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
.maxnamewidth247{
|
||||
max-width: 247px;
|
||||
overflow:hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space:nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
.maxnamewidth340{
|
||||
max-width: 340px;
|
||||
overflow:hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space:nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
5
public/react/src/common/Component.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import md5 from 'md5';
|
||||
export function setmiyah(logins){
|
||||
const opens ="79e33abd4b6588941ab7622aed1e67e8";
|
||||
return md5(opens+logins);
|
||||
}
|
||||
19
public/react/src/common/Const.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
EDU_ADMIN = 1 # 超级管理员
|
||||
EDU_BUSINESS = 2 # 运营人员
|
||||
EDU_SHIXUN_MANAGER = 3 # 实训管理员
|
||||
EDU_SHIXUN_MEMBER = 4 # 实训成员
|
||||
EDU_CERTIFICATION_TEACHER = 5 # 平台认证的老师
|
||||
EDU_GAME_MANAGER = 6 # TPI的创建者
|
||||
EDU_TEACHER = 7 # 平台老师,但是未认证
|
||||
EDU_NORMAL = 8 # 普通用户
|
||||
*/
|
||||
|
||||
export const EDU_ADMIN = 1 // 超级管理员
|
||||
export const EDU_BUSINESS = 2 // # 运营人员
|
||||
export const EDU_SHIXUN_MANAGER = 3 // 实训管理员
|
||||
export const EDU_SHIXUN_MEMBER = 4 // 实训成员
|
||||
export const EDU_CERTIFICATION_TEACHER = 5 // 平台认证的老师
|
||||
export const EDU_GAME_MANAGER = 6 // TPI的创建者
|
||||
export const EDU_TEACHER = 7 // 平台老师,但是未认证
|
||||
export const EDU_NORMAL = 8 // 普通用户
|
||||
69
public/react/src/common/DateUtil.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import moment from "moment";
|
||||
|
||||
// 处理整点 半点
|
||||
// 取传入时间往后的第一个半点
|
||||
export function handleDateString(dateString) {
|
||||
if (!dateString) return dateString;
|
||||
const ar = dateString.split(':')
|
||||
if (ar[1] == '00' || ar[1] == '30') {
|
||||
return dateString
|
||||
}
|
||||
const miniute = parseInt(ar[1]);
|
||||
if (miniute < 30 || miniute == 60) {
|
||||
return [ar[0], '30'].join(':')
|
||||
}
|
||||
if (miniute < 60) {
|
||||
// 加一个小时
|
||||
const tempStr = [ar[0], '00'].join(':');
|
||||
const format = "YYYY-MM-DD HH:mm";
|
||||
const _moment = moment(tempStr, format)
|
||||
_moment.add(1, 'hours')
|
||||
return _moment.format(format)
|
||||
}
|
||||
|
||||
return dateString
|
||||
}
|
||||
|
||||
// 给moment对象取下一个半点或整点
|
||||
export function getNextHalfHourOfMoment(moment) {
|
||||
if (!moment) {
|
||||
return moment
|
||||
}
|
||||
const minutes = moment.minutes()
|
||||
if (minutes < 30) {
|
||||
moment.minutes(30)
|
||||
} else if (minutes < 60) {
|
||||
moment.minutes(0).add(1, 'hours')
|
||||
}
|
||||
return moment
|
||||
}
|
||||
|
||||
export function formatDuring(mss){
|
||||
var days = parseInt(mss / (1000 * 60 * 60 * 24));
|
||||
var hours = parseInt((mss % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
||||
var minutes = parseInt((mss % (1000 * 60 * 60)) / (1000 * 60));
|
||||
// console.log("formatDuringformatDuring");
|
||||
// console.log(days);
|
||||
// console.log(hours);
|
||||
// console.log(minutes);
|
||||
// console.log(Math.abs(days));
|
||||
// console.log(Math.abs(hours));
|
||||
// console.log(Math.abs(minutes));
|
||||
|
||||
try {
|
||||
days = Math.abs(days);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
try {
|
||||
hours = Math.abs(hours);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
try {
|
||||
minutes = Math.abs(minutes);
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
return days + "天" + hours + "小时" + minutes + "分";
|
||||
}
|
||||
8
public/react/src/common/Env.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export function isDev() {
|
||||
return window.location.port === "3007";
|
||||
}
|
||||
|
||||
// const isMobile
|
||||
export const isMobile = (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(navigator.userAgent.toLowerCase()));
|
||||
|
||||
// const isWeiXin = (/MicroMessenger/i.test(navigator.userAgent.toLowerCase()));
|
||||
70
public/react/src/common/EventUtil.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const $ = window.$;
|
||||
export function trigger(eventName, data) {
|
||||
$(window).trigger(eventName, data);
|
||||
}
|
||||
|
||||
export function on(eventName, callback) {
|
||||
$(window).on(eventName, (event, data)=>{
|
||||
callback && callback(event, data)
|
||||
});
|
||||
}
|
||||
|
||||
export function off(eventName) {
|
||||
$(window).off(eventName);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/28230845/communication-between-tabs-or-windows
|
||||
const broadcastChannelMap = {}
|
||||
|
||||
const localStorageMap = {}
|
||||
function postMessageByLocalStorage(eventName, message) {
|
||||
console.log('storage event trigger:', eventName)
|
||||
localStorage.setItem(eventName, JSON.stringify(message));
|
||||
}
|
||||
function onMessageByLocalStorage(eventName, callback) {
|
||||
console.log('storage event register:', eventName)
|
||||
localStorageMap[eventName] = callback;
|
||||
}
|
||||
window.addEventListener("storage", function(ev) {
|
||||
const cb = localStorageMap[ev.key];
|
||||
// console.log('storage event:', ev)
|
||||
if (cb) {
|
||||
cb(JSON.parse(ev.newValue))
|
||||
}
|
||||
});
|
||||
export function broadcastChannelPostMessage(eventName, message) {
|
||||
if (!window.BroadcastChannel) {
|
||||
console.error('浏览器不支持BroadcastChannel')
|
||||
|
||||
postMessageByLocalStorage(eventName, message)
|
||||
return;
|
||||
}
|
||||
var bc;
|
||||
if (!broadcastChannelMap[eventName]) {
|
||||
bc = new window.BroadcastChannel(eventName);
|
||||
broadcastChannelMap[eventName] = bc
|
||||
} else {
|
||||
bc = broadcastChannelMap[eventName]
|
||||
}
|
||||
bc.postMessage(message); /* send */
|
||||
|
||||
}
|
||||
|
||||
export function broadcastChannelOnmessage(eventName, callback) {
|
||||
if (!window.BroadcastChannel) {
|
||||
console.error('浏览器不支持BroadcastChannel')
|
||||
onMessageByLocalStorage(eventName, callback)
|
||||
return;
|
||||
}
|
||||
var bc;
|
||||
if (!broadcastChannelMap[eventName]) {
|
||||
bc = new window.BroadcastChannel(eventName);
|
||||
broadcastChannelMap[eventName] = bc
|
||||
} else {
|
||||
bc = broadcastChannelMap[eventName]
|
||||
}
|
||||
bc.onmessage = function (ev) {
|
||||
console.log(ev);
|
||||
callback && callback(ev)
|
||||
}
|
||||
}
|
||||
28
public/react/src/common/IEVersion.js
Normal file
@@ -0,0 +1,28 @@
|
||||
export function IEVersion(){
|
||||
var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串
|
||||
var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1; //判断是否IE<11浏览器
|
||||
var isEdge = userAgent.indexOf("Edge") > -1 && !isIE; //判断是否IE的Edge浏览器
|
||||
var isIE11 = userAgent.indexOf('Trident') > -1 && userAgent.indexOf("rv:11.0") > -1;
|
||||
if(isIE) {
|
||||
var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
|
||||
reIE.test(userAgent);
|
||||
var fIEVersion = parseFloat(RegExp["$1"]);
|
||||
if(fIEVersion == 7) {
|
||||
return 7;
|
||||
} else if(fIEVersion == 8) {
|
||||
return 8;
|
||||
} else if(fIEVersion == 9) {
|
||||
return 9;
|
||||
} else if(fIEVersion == 10) {
|
||||
return 10;
|
||||
} else {
|
||||
return 6;//IE版本<=7
|
||||
}
|
||||
} else if(isEdge) {
|
||||
return 'edge';//edge
|
||||
} else if(isIE11) {
|
||||
return 11; //IE11
|
||||
}else{
|
||||
return -1;//不是ie浏览器
|
||||
}
|
||||
}
|
||||
29
public/react/src/common/LoadingSpin.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React, { Component } from 'react';
|
||||
import {Spin} from 'antd';
|
||||
class LoadingSpin extends Component{
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
render(){
|
||||
const { style } = this.props;
|
||||
return(
|
||||
<div className="edu-tab-con-box clearfix edu-txt-center" style={style}>
|
||||
<style>
|
||||
{`
|
||||
.edu-tab-con-box{
|
||||
padding:100px 0px;
|
||||
}
|
||||
.ant-modal-body .edu-tab-con-box{
|
||||
padding:0px!important;
|
||||
}
|
||||
img.edu-nodata-img{
|
||||
margin: 40px auto 20px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<Spin tip="正在获取相关数据..."/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default LoadingSpin;
|
||||
61
public/react/src/common/LogUtil.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import moment from 'moment'
|
||||
const log = require('loglevel');
|
||||
log.enableAll();
|
||||
|
||||
// 获取后可以改变日志级别
|
||||
window.getLog = () => {
|
||||
return log;
|
||||
}
|
||||
window._logWithTimeStamp = true;
|
||||
|
||||
const timeStamp = () => {
|
||||
if (window._logWithTimeStamp) {
|
||||
return `[${moment().format('hh:mm:ss')}] `
|
||||
}
|
||||
return ''
|
||||
}
|
||||
/*
|
||||
带trace的、默认折叠起来的控制台输出
|
||||
第一个参数最好传入string类型的标识,接着可以跟任意类型任意个数的参数,各个参数都会打印到控制台
|
||||
*/
|
||||
export function trace_collapse(content) {
|
||||
if (console.groupCollapsed) {
|
||||
console.groupCollapsed(typeof content == 'string' ? content : 'trace_collapse');
|
||||
log.trace(arguments);
|
||||
console.groupEnd();
|
||||
} else {
|
||||
trace(content)
|
||||
}
|
||||
}
|
||||
|
||||
export function trace(content) {
|
||||
log.trace(content);
|
||||
}
|
||||
export function debug(content) {
|
||||
log.debug(content);
|
||||
}
|
||||
export function info(content) {
|
||||
log.info(content);
|
||||
}
|
||||
export function warn(content) {
|
||||
log.warn(content);
|
||||
}
|
||||
export function error(content) {
|
||||
log.error(content);
|
||||
}
|
||||
|
||||
export function trace_c(content) {
|
||||
log.trace(`${timeStamp()}%c${content}`, 'color:magenta;');
|
||||
}
|
||||
export function debug_c(content) {
|
||||
log.debug(`${timeStamp()}%c${content}`, 'color:cyan;');
|
||||
}
|
||||
export function info_c(content) {
|
||||
log.info(`${timeStamp()}%c${content}`, 'color:blue;');
|
||||
}
|
||||
export function warn_c(content) {
|
||||
log.warn(`${timeStamp()}%c${content}`, 'color:crimson;');
|
||||
}
|
||||
export function error_c(content) {
|
||||
log.error(`${timeStamp()}%c${content}`, 'color:red;');
|
||||
}
|
||||
15
public/react/src/common/RouterUtil.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { queryString } from 'educoder'
|
||||
export function updatePageParams(pageNum, props) {
|
||||
const url = props.match.url
|
||||
|
||||
const _search = props.location.search;
|
||||
let parsed = {};
|
||||
if (_search) {
|
||||
parsed = queryString.parse(_search);
|
||||
}
|
||||
|
||||
// 修改page參數
|
||||
parsed.page = pageNum
|
||||
|
||||
props.history.push(`${url}?${queryString.stringify(parsed)}`)
|
||||
}
|
||||
33
public/react/src/common/ShowSpin.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { Component } from 'react';
|
||||
import { SnackbarHOC } from 'educoder';
|
||||
import { TPMIndexHOC } from '../modules/tpm/TPMIndexHOC';
|
||||
import {Spin,Alert} from 'antd';
|
||||
|
||||
class ShowSpin extends Component{
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
|
||||
|
||||
render() {
|
||||
let marigin={
|
||||
width: '100%',
|
||||
minHeight: '500px',
|
||||
}
|
||||
return (
|
||||
|
||||
<Spin style={marigin}>
|
||||
|
||||
<Alert
|
||||
style={marigin}
|
||||
type="info"
|
||||
/>
|
||||
|
||||
</Spin>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default SnackbarHOC() ( TPMIndexHOC(ShowSpin) );
|
||||
91
public/react/src/common/SnackbarHOC.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { Component } from 'react';
|
||||
import Snackbar from 'material-ui/Snackbar';
|
||||
import Fade from 'material-ui/transitions/Fade';
|
||||
import { notification } from 'antd'
|
||||
export function SnackbarHOC(options = {}) {
|
||||
return function wrap(WrappedComponent) {
|
||||
return class Wrapper extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.showSnackbar = this.showSnackbar.bind(this)
|
||||
this.state = {
|
||||
snackbarText: '',
|
||||
snackbarOpen: false,
|
||||
}
|
||||
}
|
||||
|
||||
handleSnackbarClose() {
|
||||
this.setState({
|
||||
snackbarOpen: false,
|
||||
snackbarVertical: '',
|
||||
snackbarHorizontal: '',
|
||||
})
|
||||
}
|
||||
|
||||
// 全局的snackbar this.props.showSnackbar调用即可
|
||||
// showSnackbar(description, message = "提示",icon) {
|
||||
// // this.setState({
|
||||
// // snackbarOpen: true,
|
||||
// // snackbarText: text,
|
||||
// // snackbarVertical: vertical,
|
||||
// // snackbarHorizontal: horizontal,
|
||||
// // })
|
||||
// const data = {
|
||||
// message,
|
||||
// description
|
||||
// }
|
||||
// if (icon) {
|
||||
// data.icon = icon;
|
||||
// }
|
||||
// notification.open(data);
|
||||
// }
|
||||
|
||||
showSnackbar(text, vertical, horizontal) {
|
||||
this.setState({
|
||||
snackbarOpen: true,
|
||||
snackbarText: text,
|
||||
snackbarVertical: vertical,
|
||||
snackbarHorizontal: horizontal,
|
||||
})
|
||||
}
|
||||
//个别情况需要走
|
||||
showNotification = (description, message = "提示", icon) => {
|
||||
const data = {
|
||||
message,
|
||||
description
|
||||
}
|
||||
if (icon) {
|
||||
data.icon = icon;
|
||||
}
|
||||
notification.open(data);
|
||||
}
|
||||
render() {
|
||||
const { snackbarOpen, snackbarText, snackbarHorizontal, snackbarVertical } = this.state;
|
||||
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Snackbar
|
||||
className={"rootSnackbar"}
|
||||
style={{zIndex:30000}}
|
||||
open={this.state.snackbarOpen}
|
||||
autoHideDuration={3000}
|
||||
anchorOrigin={{ vertical: this.state.snackbarVertical || 'top'
|
||||
, horizontal: this.state.snackbarHorizontal || 'center' }}
|
||||
onClose={() => this.handleSnackbarClose()}
|
||||
transition={Fade}
|
||||
SnackbarContentProps={{
|
||||
'aria-describedby': 'message-id',
|
||||
}}
|
||||
resumeHideDuration={2000}
|
||||
message={<span id="message-id">{this.state.snackbarText}</span>}
|
||||
/>
|
||||
<WrappedComponent {...this.props} showSnackbar={ this.showSnackbar } showNotification= { this.showNotification } >
|
||||
|
||||
</WrappedComponent>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
public/react/src/common/Store.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import store from 'store'
|
||||
|
||||
export function toStore(key, val) {
|
||||
let _config = store.get('__ec');
|
||||
if (!_config) _config = {};
|
||||
_config[key] = val;
|
||||
store.set('__ec', _config)
|
||||
}
|
||||
|
||||
export function fromStore(key, defaultVal) {
|
||||
let _config = store.get('__ec');
|
||||
if (!_config) return defaultVal;
|
||||
return _config[key] === undefined ? defaultVal : _config[key];
|
||||
}
|
||||
79
public/react/src/common/TextUtil.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { bytesToSize, getUrl, getUrl2 } from 'educoder';
|
||||
const $ = window.$
|
||||
|
||||
export function isImageExtension(fileName) {
|
||||
return fileName ? !!(fileName.match(/.(jpg|jpeg|png|gif)$/i)) : false
|
||||
}
|
||||
|
||||
export function markdownToHTML(oldContent, selector) {
|
||||
window.$('#md_div').html('')
|
||||
// markdown to html
|
||||
if (selector && oldContent && oldContent.startsWith('<p')) { // 普通html处理
|
||||
window.$('#' + selector).addClass('renderAsHtml')
|
||||
window.$('#' + selector).html(oldContent)
|
||||
} else {
|
||||
try {
|
||||
$("#"+selector).html('')
|
||||
// selector ||
|
||||
var markdwonParser = window.editormd.markdownToHTML(selector || "md_div", {
|
||||
markdown: oldContent, // .replace(/▁/g,"▁▁▁"),
|
||||
emoji: true,
|
||||
htmlDecode: "style,script,iframe", // you can filter tags decode
|
||||
taskList: true,
|
||||
tex: true, // 默认不解析
|
||||
flowChart: true, // 默认不解析
|
||||
sequenceDiagram: true // 默认不解析
|
||||
});
|
||||
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
}
|
||||
// selector = '.' + selector
|
||||
if (selector) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = window.$('#md_div').html()
|
||||
if (selector) {
|
||||
window.$(selector).html(content)
|
||||
}
|
||||
return content
|
||||
}
|
||||
}
|
||||
function _doDownload(options) {
|
||||
$.fileDownload(getUrl() + "/api" + options.url, {
|
||||
successCallback: options.successCallback,
|
||||
failCallback: options.failCallback
|
||||
});
|
||||
}
|
||||
export function downloadFile(options) {
|
||||
if ($.fileDownload) {
|
||||
_doDownload(options)
|
||||
} else {
|
||||
const _url_origin = getUrl2()
|
||||
$.getScript(
|
||||
`${_url_origin}/javascripts/download/jquery.fileDownload.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
_doDownload(options)
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function appendFileSizeToUploadFile(item) {
|
||||
return `${item.title}${uploadNameSizeSeperator}${item.filesize}`
|
||||
}
|
||||
export function appendFileSizeToUploadFileAll(fileList) {
|
||||
return fileList.map(item => {
|
||||
if (item.name.indexOf(uploadNameSizeSeperator) == -1) {
|
||||
return Object.assign({}, item, {name: `${item.name}${uploadNameSizeSeperator}${bytesToSize(item.size)}`})
|
||||
}
|
||||
return item
|
||||
})
|
||||
}
|
||||
export const uploadNameSizeSeperator = ' '
|
||||
|
||||
export const sortDirections = ["ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend",
|
||||
"ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend",
|
||||
"ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend",
|
||||
"ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", "ascend", "descend", ]
|
||||
6
public/react/src/common/UnitUtil.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export function bytesToSize(bytes) {
|
||||
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (bytes == 0) return '0 Byte';
|
||||
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||
return parseFloat(bytes / Math.pow(1024, i), 2).toFixed(1) + ' ' + sizes[i];
|
||||
}
|
||||
187
public/react/src/common/UrlTool.js
Normal file
@@ -0,0 +1,187 @@
|
||||
import React from "react";
|
||||
import md5 from 'md5';
|
||||
import {Input} from "antd";
|
||||
const { Search } = Input;
|
||||
|
||||
const $ = window.$;
|
||||
const isDev = window.location.port == 3007;
|
||||
export const TEST_HOST = "https://test-newweb.educoder.net"
|
||||
export function getImageUrl(path) {
|
||||
// https://www.educoder.net
|
||||
// https://testbdweb.trustie.net
|
||||
// const local = 'http://localhost:3000'
|
||||
const local = 'https://test-newweb.educoder.net'
|
||||
if (isDev) {
|
||||
return `${local}/${path}`
|
||||
}
|
||||
return `/${path}`;
|
||||
}
|
||||
|
||||
export function setImagesUrl(path){
|
||||
const local = 'https://test-newweb.educoder.net'
|
||||
let firstStr=path.substr(0,1);
|
||||
// console.log(firstStr);
|
||||
if(firstStr=="/"){
|
||||
return isDev?`${local}${path}`:`${path}`;
|
||||
}else{
|
||||
return isDev?`${local}/${path}`:`/${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function getUrl(path, goTest) {
|
||||
// https://www.educoder.net
|
||||
// https://testbdweb.trustie.net
|
||||
|
||||
// 如果想所有url定位到测试版,可以反注释掉下面这行
|
||||
//goTest = true
|
||||
// testbdweb.educoder.net testbdweb.trustie.net
|
||||
// const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000'
|
||||
// const local = 'https://testeduplus2.educoder.net'
|
||||
const local = 'http://localhost:3007'
|
||||
if (isDev) {
|
||||
return `${local}${path?path:''}`
|
||||
}
|
||||
return `${path ? path: ''}`;
|
||||
}
|
||||
|
||||
export function getUrlmys(path, goTest) {
|
||||
// https://www.educoder.net
|
||||
// https://testbdweb.trustie.net
|
||||
|
||||
// 如果想所有url定位到测试版,可以反注释掉下面这行
|
||||
//goTest = true
|
||||
// testbdweb.educoder.net testbdweb.trustie.net
|
||||
// const local = goTest ? 'https://testeduplus2.educoder.net' : 'http://localhost:3000'
|
||||
// const local = 'https://testeduplus2.educoder.net'
|
||||
const local = 'https://test-jupyterweb.educoder.net'
|
||||
if (isDev) {
|
||||
return `${local}${path?path:''}`
|
||||
}
|
||||
return `${path ? path: ''}`;
|
||||
}
|
||||
export function getStaticUrl() {
|
||||
const local = TEST_HOST;
|
||||
if (isDev) {
|
||||
return local
|
||||
}
|
||||
// todo cdn
|
||||
return ''
|
||||
}
|
||||
export function getUrl2(path, goTest) {
|
||||
const local = 'http://localhost:3000'
|
||||
if (isDev) {
|
||||
return `${local}${path?path:''}`
|
||||
}
|
||||
return `${path ? path: ''}`;
|
||||
}
|
||||
const newopens ="79e33abd4b6588941ab7622aed1e67e8";
|
||||
let newtimestamp;
|
||||
let checkSubmitFlgs = false;
|
||||
function railsgettimess(proxy) {
|
||||
if(checkSubmitFlgs===false){
|
||||
$.ajax({url:proxy,
|
||||
async:false,success:function(data){
|
||||
if(data.status===0){
|
||||
newtimestamp=data.message;
|
||||
checkSubmitFlgs = true;
|
||||
}
|
||||
}})
|
||||
|
||||
window.setTimeout(function () {
|
||||
checkSubmitFlgs=false;
|
||||
}, 2500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export function Railsgettimes() {
|
||||
railsgettimess(`${getUrl()}/api/main/first_stamp.json`);
|
||||
// railsgettimess(`https://api.m.taobao.com/rest/api3.do?api=mtop.common.getTimestamp`);
|
||||
}
|
||||
export function getmyUrl(geturl) {
|
||||
|
||||
return `${getUrl()}${geturl}`;
|
||||
}
|
||||
|
||||
export function getUploadActionUrl(path, goTest) {
|
||||
Railsgettimes()
|
||||
let anewopens=md5(newopens+newtimestamp);
|
||||
return `${getUrl()}/api/attachments.json${isDev ? `?debug=${window._debugType || 'admin'}&randomcode=${newtimestamp}&client_key=${anewopens}` : `?randomcode=${newtimestamp}&client_key=${anewopens}`}`;
|
||||
}
|
||||
|
||||
export function getUploadActionUrltwo(id) {
|
||||
Railsgettimes()
|
||||
let anewopens=md5(newopens+newtimestamp);
|
||||
return `${getUrlmys()}/api/shixuns/${id}/upload_data_sets.json${isDev ? `?debug=${window._debugType || 'admin'}&randomcode=${newtimestamp}&client_key=${anewopens}` : `?randomcode=${newtimestamp}&client_key=${anewopens}`}`
|
||||
}
|
||||
|
||||
export function getUploadActionUrlthree() {
|
||||
Railsgettimes()
|
||||
let anewopens=md5(newopens+newtimestamp);
|
||||
return `${getUrlmys()}/api/jupyters/import_with_tpm.json${isDev ? `?debug=${window._debugType || 'admin'}&randomcode=${newtimestamp}&client_key=${anewopens}` : `?randomcode=${newtimestamp}&client_key=${anewopens}`}`
|
||||
}
|
||||
|
||||
export function getUploadActionUrlOfAuth(id) {
|
||||
Railsgettimes()
|
||||
let anewopens=md5(newopens+newtimestamp);
|
||||
return `${getUrl()}/api/users/accounts/${id}/auth_attachment.json${isDev ? `?debug=${window._debugType || 'admin'}&randomcode=${newtimestamp}&client_key=${anewopens}` : `?randomcode=${newtimestamp}&client_key=${anewopens}`}`
|
||||
}
|
||||
|
||||
export function getRandomNumber(type) {
|
||||
Railsgettimes()
|
||||
let anewopens=md5(newopens+newtimestamp);
|
||||
return type===true?`randomcode=${newtimestamp}&client_key=${anewopens}`:`?randomcode=${newtimestamp}&client_key=${anewopens}`
|
||||
}
|
||||
|
||||
export function test(path) {
|
||||
return `${path}`;
|
||||
}
|
||||
|
||||
export function toPath(path) {
|
||||
window.open(path, '_blank');
|
||||
}
|
||||
|
||||
|
||||
export function getTaskUrlById(id) {
|
||||
return `/tasks/${id}`
|
||||
}
|
||||
|
||||
export function getRandomcode(url) {
|
||||
Railsgettimes()
|
||||
let anewopens=md5(newopens+newtimestamp);
|
||||
|
||||
if (url.indexOf('?') == -1) {
|
||||
return `${url}?randomcode=${newtimestamp}&client_key=${anewopens}`
|
||||
}else {
|
||||
return `${url}&randomcode=${newtimestamp}&client_key=${anewopens}`
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function htmlEncode(str) {
|
||||
var s = "";
|
||||
if (str.length === 0) {
|
||||
return "";
|
||||
}
|
||||
s = str.replace(/&/g, "&");
|
||||
s = s.replace(/</g, "<");
|
||||
s = s.replace(/>/g, ">");
|
||||
s = s.replace(/ /g, " ");
|
||||
s = s.replace(/\'/g, "'");//IE下不支持实体名称
|
||||
s = s.replace(/\"/g, """);
|
||||
return s;
|
||||
}
|
||||
|
||||
export function publicSearchs(Placeholder,onSearch,onInputs,onChanges,loadings) {
|
||||
return(<Search
|
||||
placeholder= { Placeholder || "请输入内容进行搜索" }
|
||||
onSearch={onSearch}
|
||||
// value={searchValue}
|
||||
onInput={onInputs}
|
||||
onChange={onChanges}
|
||||
loading={loadings||false}
|
||||
allowClear={true}
|
||||
></Search>)
|
||||
}
|
||||
51
public/react/src/common/UrlTool2.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const queryString = {
|
||||
stringify: function(params) {
|
||||
let paramsUrl = '';
|
||||
for (let key in params) {
|
||||
// https://stackoverflow.com/questions/6566456/how-to-serialize-an-object-into-a-list-of-url-query-parameters
|
||||
if (params[key] != undefined) {
|
||||
if (params[key].constructor === Array) {
|
||||
for (let singleArrIndex of params[key]) {
|
||||
paramsUrl = paramsUrl + key + '[]=' + singleArrIndex + '&'
|
||||
}
|
||||
} else {
|
||||
paramsUrl += `${key}=${encodeURIComponent(params[key])}&`
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paramsUrl == '') {
|
||||
return '';
|
||||
}
|
||||
paramsUrl = paramsUrl.substring(0, paramsUrl.length - 1);
|
||||
return paramsUrl;
|
||||
},
|
||||
parse: function(search) {
|
||||
// ?a=1&b=2
|
||||
if (!search) {
|
||||
return {}
|
||||
}
|
||||
if (search.startsWith('?')) {
|
||||
search = search.substring(1);
|
||||
}
|
||||
if (!search) {
|
||||
return {}
|
||||
}
|
||||
const keyValArray = search.split('&');
|
||||
const result = {}
|
||||
keyValArray.forEach(keyValItem => {
|
||||
const keyAndVal = keyValItem.split('=');
|
||||
result[keyAndVal[0]] = keyAndVal[1]
|
||||
})
|
||||
return result;
|
||||
}
|
||||
}
|
||||
/*
|
||||
query-string用不了
|
||||
|
||||
Failed to minify the code from this file:
|
||||
|
||||
./node_modules/_query-string@6.1.0@query-string/index.js:8
|
||||
|
||||
Read more here: http://bit.ly/2tRViJ9
|
||||
*/
|
||||
module.exports = queryString
|
||||
28
public/react/src/common/components/ConditionToolTip.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import React,{ Component } from "react";
|
||||
import { Modal,Input, Tooltip} from "antd";
|
||||
|
||||
class ConditionToolTip extends Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state={
|
||||
}
|
||||
}
|
||||
render(){
|
||||
|
||||
let { condition } = this.props;
|
||||
return(
|
||||
<React.Fragment>
|
||||
{
|
||||
condition ?
|
||||
<Tooltip placement="bottom" {...this.props}>
|
||||
{this.props.children}
|
||||
</Tooltip> :
|
||||
<React.Fragment>
|
||||
{this.props.children}
|
||||
</React.Fragment>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default ConditionToolTip;
|
||||
288
public/react/src/common/components/Cropper.js
Normal file
@@ -0,0 +1,288 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { getUrl2, isDev } from 'educoder'
|
||||
const $ = window.$
|
||||
|
||||
let _url_origin = getUrl2()
|
||||
// let _url_origin = `http://47.96.87.25:48080`;
|
||||
|
||||
|
||||
|
||||
function save_avatar(){
|
||||
|
||||
// if($(img_lg).html().trim() == ""){
|
||||
// $("#avatar-name").html("请先选择图片上传").css("color", 'red');
|
||||
// } else {
|
||||
// $("#avatar-name").html("").css("color", '#333');
|
||||
const previewId = this.props.previewId
|
||||
var img_lg = document.getElementById(previewId || 'img-preview');
|
||||
// 截图小的显示框内的内容
|
||||
window.html2canvas(img_lg).then(function(canvas) {
|
||||
// for test
|
||||
// document.getElementById('canvasWrap').appendChild(canvas);
|
||||
|
||||
var dataUrl = canvas.toDataURL("image/jpeg");
|
||||
console.log(dataUrl)
|
||||
// TODO upload base64 image data to server
|
||||
});
|
||||
return
|
||||
|
||||
// 老版接口:
|
||||
// html2canvas(img_lg, {
|
||||
// allowTaint: true,
|
||||
// taintTest: false,
|
||||
// onrendered: function(canvas) {
|
||||
// canvas.id = "mycanvas";
|
||||
// //生成base64图片数据
|
||||
// var dataUrl = canvas.toDataURL("image/jpeg");
|
||||
// console.log(dataUrl)
|
||||
|
||||
// var newImg = document.getElementById("showImg");
|
||||
// newImg.src = dataUrl;
|
||||
// return;
|
||||
|
||||
// imagesAjax(dataUrl);
|
||||
// $(".avatar-save").attr("disabled","true");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
/**
|
||||
props 说明:
|
||||
imageId 源图片标签的id
|
||||
previewId crop后预览dom的id
|
||||
imageSrc 源图片src
|
||||
width 数字格式
|
||||
height 数字格式
|
||||
*/
|
||||
class Cropper extends Component {
|
||||
state = {
|
||||
};
|
||||
|
||||
handleChange = (info) => {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.options = {
|
||||
aspectRatio: 1,
|
||||
crop(event) {
|
||||
// console.log(event.detail.x);
|
||||
// console.log(event.detail.y);
|
||||
// console.log(event.detail.width);
|
||||
// console.log(event.detail.height);
|
||||
// console.log(event.detail.rotate);
|
||||
// console.log(event.detail.scaleX);
|
||||
// console.log(event.detail.scaleY);
|
||||
},
|
||||
preview: this.props.previewId ? `#${this.props.previewId}` : '.img-preview',
|
||||
}
|
||||
|
||||
if (!window.Cropper) {
|
||||
$.ajaxSetup({
|
||||
cache: true
|
||||
});
|
||||
const _isDev = isDev()
|
||||
let _path = _isDev ? 'public' : 'build'
|
||||
|
||||
$('head').append($('<link rel="stylesheet" type="text/css" />')
|
||||
.attr('href', `${_url_origin}/react/${_path}/js/cropper/cropper.min.css`));
|
||||
|
||||
$.getScript(
|
||||
`${_url_origin}/react/${_path}/js/cropper/cropper.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
|
||||
});
|
||||
$.getScript(
|
||||
`${_url_origin}/react/${_path}/js/cropper/html2canvas.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const image = document.getElementById(this.props.imageId || '__image');
|
||||
this.cropper = new window.Cropper(image, this.options);
|
||||
}, 1200)
|
||||
}
|
||||
|
||||
renew = (image) => {
|
||||
this.cropper && this.cropper.destroy();
|
||||
this.cropper = new window.Cropper(image, this.options);
|
||||
|
||||
}
|
||||
render() {
|
||||
|
||||
const { width, height, previewId, imageSrc } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* This rule is very important, please do not ignore this! */}
|
||||
<style>{`
|
||||
.wrapper {
|
||||
width: ${ width ? (width+'px') : '500px'};
|
||||
height: ${ height ? (height+'px') : '500px'};
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.preview-lg {
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
}
|
||||
`}</style>
|
||||
<div className="wrapper">
|
||||
{/* http://localhost:3007/images/footNavLogo.png 图片转了后不对
|
||||
|| "/images/testPicture.jpg"
|
||||
|| "/images/shixun0.jpg"
|
||||
*/}
|
||||
<img id={this.props.imageId || "__image"} src={`${imageSrc }`}></img>
|
||||
</div>
|
||||
{/* background: 'aquamarine',
|
||||
'border-radius': '128px'
|
||||
*/}
|
||||
{!previewId && <div id="img-preview" className="img-preview preview-lg" style={{width: '128px', height: '128px', }}>
|
||||
</div>}
|
||||
|
||||
{/* <img id="showImg" src="http://localhost:3007/images/testPicture.jpg"></img> */}
|
||||
|
||||
{/* <div id="canvasWrap"></div> */}
|
||||
{/* <button onClick={save_avatar.bind(this)}>save </button> */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Cropper;
|
||||
|
||||
|
||||
// function aaa () {
|
||||
// function showedit_headphoto() {
|
||||
// var html = `
|
||||
// <script src=\"../head/jquery.min.js\"><\/script>\n
|
||||
// <link href=\"../head/cropper.min.css\" rel=\"stylesheet\">\n
|
||||
// <link href=\"../head/sitelogo.css\" rel=\"stylesheet\">\n
|
||||
// <script src=\"../head/bootstrap.min.js\"><\/script>\n
|
||||
// <script src=\"../head/cropper.js\"><\/script>\n
|
||||
// <script src=\"../head/sitelogo.js\"><\/script>\n
|
||||
// <script src=\"../head/html2canvas.min.js\" type=\"text/javascript\" charset=\"utf-8\"><\/script>\n\n
|
||||
|
||||
// <div class=\"task-popup\" style=\"width: 550px;\">\n <div class=\"task-popup-title clearfix task-popup-bggrey\">上传头像<\/div>\n <div class=\"clearfix\">\n
|
||||
// <div class=\"modal fade\" style=\"outline: none;\" id=\"avatar-modal\" aria-hidden=\"true\" aria-labelledby=\"avatar-modal-label\" role=\"dialog\" tabindex=\"-1\">\n
|
||||
// <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <form class=\"avatar-form\">\n <div class=\"modal-body\">\n
|
||||
// <div class=\"padding20\">\n <div class=\"avatar-upload\">\n <input class=\"avatar-src\" name=\"avatar_src\" type=\"hidden\">\n
|
||||
// <input class=\"avatar-data\" name=\"avatar_data\" type=\"hidden\">\n\n <span id=\"avatar-name\"><\/span>\n
|
||||
// <input class=\"avatar-input\" style=\"display:none;\" id=\"avatarInput\" value=\"avatars/User/116\" name=\"avatar_file\" type=\"file\">\n
|
||||
// <input type=\"hidden\" id=\"source_id\" value=\"116\"/>\n <input type=\"hidden\" id=\"source_type\" value=\"User\"/>\n <\/div>\n
|
||||
// <div class=\"row clearfix mt20 pl20\">\n <div class=\"task-form-45 fl panel-box-sizing uplaodImg\">\n
|
||||
// <div class=\"avatar-wrapper\" id=\"wrapper_image_show\">\n <!--<span style=\"display: block;\">\n 选择你要上传的图片<br/>
|
||||
// \n 仅支持JPG、GIF、PNG,且文件小于2M\n <\/span>-->\n <\/div>\n
|
||||
// <div class=\"row avatar-btns clearfix\">\n <a href=\"javascript:void(0);\" class=\"fl\" type=\"button\" onClick=\"$(\'input[id=avatarInput]\').click();\">重新上传<\/a>\n
|
||||
// <!--<div class=\"btn-group\">\n <a href=\"javascript:void(0);\" class=\"fa fa-repeat fr mt5 color-grey-9\" data-method=\"rotate\" data-option=\"90\"
|
||||
// type=\"button\" title=\"Rotate 90 degrees\">\n <\/a>\n <\/div>-->\n <\/div>\n <\/div>\n
|
||||
// <div class=\"task-form-50 panel-box-sizing fr color-grey pr20\" style=\"width: 128px;\">\n <div class=\"edu-txt-center\">\n
|
||||
// <div class=\"avatar-preview preview-lg radius\" id=\"imageHead\">\n
|
||||
// <img alt=\"头像\" height=\"128\" nhname=\"avatar_image\" src=\"/images/avatars/User/116?1556802838\" width=\"128\" />\n <\/div>\n
|
||||
// <span>头像预览<\/span>\n <\/div>\n
|
||||
// <p class=\"color-grey-9 font-12 mt110 justify\">仅支持JPG、GIF、PNG,且文件小于2M<\/p>\n <\/div>\n <\/div>\n
|
||||
// <\/div>\n <div class=\"clearfix edu-txt-center mb20 mt10\">\n
|
||||
// <a href=\"javascript:void(0);\" class=\"task-btn mr20\" onclick=\"hideModal()\">取消<\/a>\n
|
||||
// <a href=\"javascript:void(0);\" class=\"avatar-save task-btn task-btn-orange\" data-dismiss=\"modal\">确定<\/a>\n
|
||||
// <\/div>\n <\/div>\n <\/form>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n<\/div>\n\n<script>\n
|
||||
// $(function () {\n new CropAvatar($(\'#crop-avatar\'), 1/1);\n\n //---------------------------头像上传-----------------------------//\n //做个下简易的验证 大小 格式\n $(\'#avatarInput\').on(\'change\', function(e) {\n var filemaxsize = 1024 * 5;//5M\n var target = $(e.target);\n var Size = target[0].files[0].size / 1024;\n if(Size > filemaxsize) {\n alert(\'图片过大,请重新选择!\');\n $(\".avatar-wrapper\").children().remove;\n return false;\n }\n if(!this.files[0].type.match(/image.*/)) {\n alert(\'请选择正确的图片!\')\n } else {\n /*var filename = document.querySelector(\"#avatar-name\");*/\n var texts = document.querySelector(\"#avatarInput\").value;\n var teststr = texts; //你这里的路径写错了\n testend = teststr.match(/[^\\\\]+\\.[^\\(]+/i); //直接完整文件名的\n /*filename.innerHTML = testend;\n $(filename).css(\"color\", \'#333\');*/\n $(\".avatar-save\").removeClass(\"task-btn-grey\").addClass(\"task-btn-orange\");\n $(\".avatar-save\").on(\"click\", save_avatar);\n }\n\n });\n });\n\n function save_avatar(){\n var img_lg = document.getElementById(\'imageHead\');\n if($(img_lg).html().trim() == \"\"){\n $(\"#avatar-name\").html(\"请先选择图片上传\").css(\"color\", \'red\');\n } else {\n $(\"#avatar-name\").html(\"\").css(\"color\", \'#333\');\n // 截图小的显示框内的内容\n html2canvas(img_lg, {\n allowTaint: true,\n taintTest: false,\n onrendered: function(canvas) {\n canvas.id = \"mycanvas\";\n //生成base64图片数据\n var dataUrl = canvas.toDataURL(\"image/jpeg\");\n var newImg = document.createElement(\"img\");\n newImg.src = dataUrl;\n imagesAjax(dataUrl);\n $(\".avatar-save\").attr(\"disabled\",\"true\");\n }\n });\n }\n }\n\n function imagesAjax(src) {\n var data = {};\n data.img = src;\n data.source_id = $(\'#source_id\').val();\n data.source_type = $(\'#source_type\').val();\n data.is_direct = 0;\n $.ajax({\n url: \"/upload_avatar\",\n beforeSend: function(xhr) {xhr.setRequestHeader(\'X-CSRF-Token\', $(\'meta[name=\"csrf-token\"]\').attr(\'content\'))},\n data: data,\n type: \"POST\",\n success: function (re) {\n console.log(re);\n console.log(1562050370);\n if(re){\n var o = JSON.parse(re);\n if (o.status !=0 ){\n console.log(o.message);\n } else {\n var imgSpan = $(\"img[nhname=\'avatar_image\']\");\n imgSpan.attr({\"src\": o.url + \'?1562050370\'});\n $(\"#user_code\").html(o.grade);\n notice_box_redirect(\"/users/shitou\", \"上传成功\");\n }\n } else {\n notice_box(\"上传出错\");\n }\n\n },\n error: function (e) {\n alert(e);\n }\n });\n }\n
|
||||
// <\/script>`;
|
||||
// pop_box_new(html, 550, 510);
|
||||
// $("#imageHead img").attr({"src": $("#user_avatar_show").attr("src")});
|
||||
// $("#wrapper_image_show img").attr({"src": $("#user_avatar_show").attr("src")});
|
||||
|
||||
|
||||
// }
|
||||
|
||||
// $(function () {
|
||||
// new CropAvatar($('#crop-avatar'), 1/1);
|
||||
// //---------------------------头像上传-----------------------------//
|
||||
// //做个下简易的验证 大小 格式
|
||||
// $('#avatarInput').on('change', function(e) {
|
||||
// var filemaxsize = 1024 * 5;//5M
|
||||
// var target = $(e.target);
|
||||
// var Size = target[0].files[0].size / 1024;
|
||||
// if(Size > filemaxsize) {
|
||||
// alert('图片过大,请重新选择!');
|
||||
// $(".avatar-wrapper").children().remove;
|
||||
// return false;
|
||||
// }
|
||||
// if(!this.files[0].type.match(/image.*/)) {
|
||||
// alert('请选择正确的图片!')
|
||||
// } else {
|
||||
// /*var filename = document.querySelector("#avatar-name");*/
|
||||
// var texts = document.querySelector("#avatarInput").value;
|
||||
// var teststr = texts; //你这里的路径写错了
|
||||
// testend = teststr.match(/[^\\\\]+\\.[^\\(]+/i); //直接完整文件名的
|
||||
// /*filename.innerHTML = testend; $(filename).css("color", '#333');*/
|
||||
// $(".avatar-save").removeClass("task-btn-grey").addClass("task-btn-orange");
|
||||
// $(".avatar-save").on("click", save_avatar);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// function save_avatar(){
|
||||
// var img_lg = document.getElementById('imageHead');
|
||||
// if($(img_lg).html().trim() == ""){
|
||||
// $("#avatar-name").html("请先选择图片上传").css("color", 'red');
|
||||
// } else {
|
||||
// $("#avatar-name").html("").css("color", '#333');
|
||||
// // 截图小的显示框内的内容
|
||||
// html2canvas(img_lg, {
|
||||
// allowTaint: true,
|
||||
// taintTest: false,
|
||||
// onrendered: function(canvas) {
|
||||
// canvas.id = "mycanvas";
|
||||
// //生成base64图片数据
|
||||
// var dataUrl = canvas.toDataURL("image/jpeg");
|
||||
// var newImg = document.createElement("img");
|
||||
// newImg.src = dataUrl;
|
||||
// imagesAjax(dataUrl);
|
||||
// $(".avatar-save").attr("disabled","true");
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// function imagesAjax(src) {
|
||||
// var data = {};
|
||||
// data.img = src;
|
||||
// data.source_id = $('#source_id').val();
|
||||
// data.source_type = $('#source_type').val();
|
||||
// data.is_direct = 0;
|
||||
// $.ajax({
|
||||
// url: "/upload_avatar",
|
||||
// beforeSend: function(xhr) {
|
||||
// xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
|
||||
// },
|
||||
// data: data,
|
||||
// type: "POST",
|
||||
// success: function (re) {
|
||||
// console.log(re);
|
||||
// // console.log(1562050370);
|
||||
// if(re){
|
||||
// var o = JSON.parse(re);
|
||||
// if (o.status !=0 ){
|
||||
// console.log(o.message);
|
||||
// } else {
|
||||
// var imgSpan = $("img[nhname='avatar_image']");
|
||||
// imgSpan.attr({"src": o.url + '?1562050370'});
|
||||
// $("#user_code").html(o.grade);
|
||||
// notice_box_redirect("/users/shitou", "上传成功");
|
||||
// }
|
||||
// } else {
|
||||
// notice_box("上传出错");
|
||||
// }
|
||||
// },
|
||||
// error: function (e) {
|
||||
// alert(e);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
// }
|
||||
116
public/react/src/common/components/DragValidator.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
const $ = window.jQuery
|
||||
const jQuery = $;
|
||||
// if () {
|
||||
// !$.drag
|
||||
// (function($){
|
||||
// $.fn.dragValidator = function(options){
|
||||
// var x, drag = this, isMove = false, defaults = {
|
||||
// };
|
||||
// var options = $.extend(defaults, options);
|
||||
// //添加背景,文字,滑块
|
||||
// var html = '<div class="drag_bg"></div>'+
|
||||
// '<div class="drag_text" onselectstart="return false;" unselectable="on">拖动滑块验证</div>'+
|
||||
// '<div class="handler handler_bg"></div>';
|
||||
// this.append(html);
|
||||
//
|
||||
// var handler = drag.find('.handler');
|
||||
// var drag_bg = drag.find('.drag_bg');
|
||||
// var text = drag.find('.drag_text');
|
||||
// var maxWidth = text.width() - handler.width(); //能滑动的最大间距
|
||||
// //鼠标按下时候的x轴的位置
|
||||
// handler.mousedown(function(e){
|
||||
// isMove = true;
|
||||
// x = e.pageX - parseInt(handler.css('left'), 10);
|
||||
// });
|
||||
//
|
||||
// //鼠标指针在上下文移动时,移动距离大于0小于最大间距,滑块x轴位置等于鼠标移动距离
|
||||
// $(document).mousemove(function(e){
|
||||
// var _x = e.pageX - x;
|
||||
// var handler_offset = handler.offset();
|
||||
// var lastX = e.clientX -x;
|
||||
// lastX = Math.max(0,Math.min(maxWidth,lastX));
|
||||
// if(isMove){
|
||||
// if(_x > 0 && _x <= maxWidth){
|
||||
// handler.css({'left': lastX});
|
||||
// drag_bg.css({'width': lastX});
|
||||
// }
|
||||
// else if(lastX > maxWidth - 5 && lastX < maxWidth + 5 ){ //鼠标指针移动距离达到最大时清空事件
|
||||
// dragOk();
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// handler.mouseup(function(e){
|
||||
// isMove = false;
|
||||
// var _x = e.pageX - x;
|
||||
// if(text.text() != '验证通过' && _x < maxWidth){ //鼠标松开时,如果没有达到最大距离位置,滑块就返回初始位置
|
||||
// handler.animate({'left': 0});
|
||||
// drag_bg.animate({'width': 0});
|
||||
// }
|
||||
// });
|
||||
//
|
||||
// //清空事件
|
||||
// function dragOk(){
|
||||
// options.dragOkCallback && options.dragOkCallback()
|
||||
// var kuaiwidth=drag.width() - handler.width() - 2;
|
||||
// handler.removeClass('handler_bg').addClass('handler_ok_bg');
|
||||
// handler.css({'left':kuaiwidth+'px'})
|
||||
// text.css({'width':kuaiwidth+'px'});
|
||||
// text.text('验证通过');
|
||||
// drag.css({'color': '#fff'});
|
||||
// drag_bg.css({'width':kuaiwidth+'px'})
|
||||
// handler.unbind('mousedown');
|
||||
// $(document).unbind('mousemove');
|
||||
// $(document).unbind('mouseup');
|
||||
// $("#user_verification_notice").html("");
|
||||
// $('#user_verification_notice').parent().hide();
|
||||
// }
|
||||
// };
|
||||
// })(jQuery);
|
||||
// }
|
||||
|
||||
class DragValidator extends Component {
|
||||
componentDidMount () {
|
||||
// if($("#reg-drag").length>0 && IsPC()){
|
||||
// $("#reg-drag").dragValidator({
|
||||
// height: this.props.height,
|
||||
// dragOkCallback: () => {
|
||||
// this.props.dragOkCallback && this.props.dragOkCallback()
|
||||
// }
|
||||
// });
|
||||
// }else{
|
||||
// $("#reg-drag").empty();
|
||||
// }
|
||||
}
|
||||
empty() {
|
||||
// $("#reg-drag").empty();
|
||||
}
|
||||
render() {
|
||||
const height = this.props.height || 45;
|
||||
const className = this.props.className
|
||||
const successGreenColor = this.props.successGreenColor || '#29bd8b'
|
||||
// newMain clearfix
|
||||
return (
|
||||
<div id="reg-drag" style={{ width:"287px",}} className={`drag_slider ${className}`}>
|
||||
<style>{`
|
||||
.drag_slider .handler {
|
||||
height: 100%;
|
||||
}
|
||||
.drag_slider {
|
||||
height: ${height}px;
|
||||
line-height: ${height}px;
|
||||
}
|
||||
.drag_slider .drag_bg {
|
||||
height: ${height}px;
|
||||
background-color: ${successGreenColor};
|
||||
}
|
||||
`}</style>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ( DragValidator );
|
||||
31
public/react/src/common/components/LinkAfterLogin.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
// 登录后才能跳转
|
||||
class LinkAfterLogin extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
checkAuth = () => {
|
||||
if (this.props.checkIfLogin()) {
|
||||
if(this.props.checkProfileComplete){
|
||||
if(this.props.checkIfProfileCompleted()){
|
||||
this.props.history.push(this.props.to)
|
||||
}else{
|
||||
this.props.showProfileCompleteDialog();
|
||||
}
|
||||
}else{
|
||||
this.props.history.push(this.props.to)
|
||||
}
|
||||
} else {
|
||||
this.props.showLoginDialog()
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return(
|
||||
<a {...this.props} onClick={this.checkAuth}>{this.props.children}</a>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default LinkAfterLogin;
|
||||
30
public/react/src/common/components/ModalConfirm.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-13 10:28:15
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-13 10:37:17
|
||||
*/
|
||||
import { Modal } from 'antd';
|
||||
|
||||
export function ModalConfirm (
|
||||
title,
|
||||
content,
|
||||
handleOk,
|
||||
handleCancel
|
||||
) {
|
||||
|
||||
Modal.confirm({
|
||||
title,
|
||||
content,
|
||||
okText: '确定',
|
||||
cancelText: '取消',
|
||||
onOk () {
|
||||
handleOk && handleOk();
|
||||
},
|
||||
onCancel () {
|
||||
handleCancel && handleCancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
106
public/react/src/common/components/ModalHOC.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
|
||||
|
||||
export function ModalHOC(options = {}) {
|
||||
return function wrap(WrappedComponent) {
|
||||
return class Wrapper extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
titlemessage: '',
|
||||
Modallist: false,
|
||||
Modallisttype: false,
|
||||
singleButton: false
|
||||
}
|
||||
}
|
||||
|
||||
// 全局的modal this.props.showModal 调用即可
|
||||
showModal = (title, content, okCallback) => {
|
||||
this.okCallback = okCallback;
|
||||
this.setState({
|
||||
titlemessage: title,
|
||||
Modallist: content,
|
||||
Modallisttype: true,
|
||||
singleButton: false,
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
showSingleButtonModal = (title, content) => {
|
||||
this.setState({
|
||||
titlemessage: title,
|
||||
Modallist: content,
|
||||
Modallisttype: true,
|
||||
singleButton: true,
|
||||
})
|
||||
}
|
||||
|
||||
onCancel = () => {
|
||||
this.setState({
|
||||
Modallisttype:false
|
||||
})
|
||||
}
|
||||
hidemodeldelete = () => {
|
||||
if (this.okCallback) {
|
||||
this.okCallback()
|
||||
}
|
||||
|
||||
this.onCancel()
|
||||
}
|
||||
render() {
|
||||
const { titlemessage, Modallisttype, Modallist, singleButton } = this.state;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Modal
|
||||
keyboard={false}
|
||||
title={titlemessage}
|
||||
// visible={modeldelet===true&&listid===list.id?true:false}
|
||||
visible={Modallisttype}
|
||||
className={"ecmodeldelet"}
|
||||
closable={false}
|
||||
footer={null}
|
||||
>
|
||||
<div className="task-popup-content" >
|
||||
<div className="task-popup-text-center font-14">{Modallist}</div>
|
||||
</div>
|
||||
{ singleButton ? <div className="task-popup-submit clearfix"
|
||||
style={{ textAlign: 'center' }}>
|
||||
<a className="task-btn task-btn-orange"
|
||||
onClick={this.onCancel}
|
||||
>知道啦</a>
|
||||
</div> : <div className="task-popup-submit clearfix">
|
||||
<a onClick={this.onCancel} className="task-btn fl">取消</a>
|
||||
<a className="task-btn task-btn-orange fr"
|
||||
onClick={this.hidemodeldelete}
|
||||
>确定</a>
|
||||
</div> }
|
||||
</Modal>
|
||||
<WrappedComponent {...this.props}
|
||||
showModal={ this.showModal }
|
||||
showSingleButtonModal={ this.showSingleButtonModal }
|
||||
>
|
||||
|
||||
</WrappedComponent>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
import { ModalHOC } from '../common/ModalHOC'
|
||||
|
||||
export default ModalHOC() (XXXComponent) ;
|
||||
|
||||
this.props.showModal('提示', '确定要删除吗?', () => {
|
||||
this.remove(k)
|
||||
})
|
||||
|
||||
*/
|
||||
15
public/react/src/common/components/MyIcon.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* @Description: 引入阿里图标库
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-10 09:03:48
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-12 10:53:47
|
||||
*/
|
||||
import { Icon } from 'antd';
|
||||
|
||||
const MyIcon = Icon.createFromIconfontCN({
|
||||
scriptUrl: '//at.alicdn.com/t/font_1535266_i4ilpm93kp.js'
|
||||
});
|
||||
|
||||
export default MyIcon;
|
||||
48
public/react/src/common/components/Notcompleted.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Modal} from 'antd';
|
||||
import axios from 'axios';
|
||||
import '../../modules/user/common.css';
|
||||
//完善个人资料
|
||||
class Notcompleted extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
}
|
||||
|
||||
modalCancel=()=>{
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
setDownload=()=>{
|
||||
window.location.href ='/account/profile';
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
console.log(this.props)
|
||||
return(
|
||||
<Modal
|
||||
keyboard={false}
|
||||
closable={false}
|
||||
footer={null}
|
||||
destroyOnClose={true}
|
||||
title="提示"
|
||||
centered={true}
|
||||
visible={this.props.modalsType===undefined?false:this.props.modalsType}
|
||||
width="530px"
|
||||
>
|
||||
<div className="educouddiv">
|
||||
<div className={"tabeltext-alignleft mt10"}><p>您尚未完善个人资料</p></div>
|
||||
<div className={"tabeltext-alignleft mt10"}><p>请在完成资料后,提交试用申请</p></div>
|
||||
<div className="clearfix mt30 edu-txt-center">
|
||||
<a className="task-btn mr30" onClick={()=>this.modalCancel()}>取消</a>
|
||||
<a className="task-btn task-btn-orange" onClick={()=>this.setDownload()}>立即完善资料</a>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Notcompleted;
|
||||
51
public/react/src/common/components/SetAppModel.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
export function SetAppModel(options={}) {
|
||||
return function wrap(WrappedComponent) {
|
||||
return class Wrapper extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
|
||||
}
|
||||
}
|
||||
modalCancel=()=>{
|
||||
window.location.href = "/";
|
||||
}
|
||||
|
||||
setDownload=()=>{
|
||||
window.location.href ='/account/profile';
|
||||
}
|
||||
componentDidMount(){
|
||||
console.log(this.props)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { titlemessage, Modallisttype, Modallist, singleButton } = this.state;
|
||||
return (
|
||||
<Modal
|
||||
keyboard={false}
|
||||
closable={false}
|
||||
footer={null}
|
||||
destroyOnClose={true}
|
||||
title="提示"
|
||||
centered={true}
|
||||
visible={true}
|
||||
width="530px"
|
||||
>
|
||||
<div className="educouddiv">
|
||||
<div className={"tabeltext-alignleft mt10"}><p>您尚未完善个人资料</p></div>
|
||||
<div className={"tabeltext-alignleft mt10"}><p>请在完成资料后,提交试用申请</p></div>
|
||||
<div className="clearfix mt30 edu-txt-center">
|
||||
<a className="task-btn mr30" onClick={()=>this.modalCancel()}>取消</a>
|
||||
<a className="task-btn task-btn-orange" onClick={()=>this.setDownload()}>立即完善资料</a>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import React,{ Component } from "react";
|
||||
import { ConditionToolTip,getRandomNumber } from 'educoder'
|
||||
|
||||
class AttachmentsList extends Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
render(){
|
||||
let { attachments } = this.props;
|
||||
return(
|
||||
<React.Fragment>
|
||||
{
|
||||
attachments.map((item,key)=>{
|
||||
return(
|
||||
<p key={key} className="clearfix mb3">
|
||||
<a className="color-grey fl">
|
||||
<i className="font-14 color-green iconfont icon-fujian mr8"></i>
|
||||
</a>
|
||||
<ConditionToolTip title={item.title} condition={item.title && item.title.length > 30 }>
|
||||
<a href={item.url+getRandomNumber()} className="mr12 fl task-hide" length="58" target={ item.is_pdf && item.is_pdf == true ? "_blank" : "_self" } style={{"maxWidth":"432px"}}>{item.title}</a>
|
||||
</ConditionToolTip>
|
||||
<span className="color-grey mt2 color-grey-6 font-12">{item.filesize}</span>
|
||||
</p>
|
||||
)
|
||||
})
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default AttachmentsList;
|
||||
139
public/react/src/common/components/comment/CommentForm.js
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* @Description: 评论表单
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-17 17:32:55
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2020-01-06 18:42:09
|
||||
*/
|
||||
import './index.scss';
|
||||
import React, { useState } from 'react';
|
||||
import { Form, Button, Input } from 'antd';
|
||||
import QuillForEditor from '../../quillForEditor';
|
||||
// import { QuillDeltaToHtmlConverter } from 'quill-delta-to-html'
|
||||
// import {formatDelta} from './util';
|
||||
const FormItem = Form.Item;
|
||||
|
||||
function CommentForm (props) {
|
||||
|
||||
const {
|
||||
onCancel,
|
||||
onSubmit,
|
||||
form,
|
||||
type
|
||||
} = props;
|
||||
|
||||
const { getFieldDecorator } = form;
|
||||
const [ctx, setCtx] = useState('');
|
||||
const [focus, setFocus] = useState(false);
|
||||
|
||||
const options = [
|
||||
// ['bold', 'italic', 'underline'],
|
||||
// [{header: [1,2,3,false]}],
|
||||
'code-block',
|
||||
'link',
|
||||
'image',
|
||||
'formula'
|
||||
];
|
||||
// const { form: { getFieldDecorator } } = props;
|
||||
const [showQuill, setShowQuill] = useState(false);
|
||||
// 点击输入框
|
||||
const handleInputClick = (type) => {
|
||||
setShowQuill(true);
|
||||
setFocus(true);
|
||||
}
|
||||
// 取消
|
||||
const handleCancle = () => {
|
||||
setShowQuill(false);
|
||||
setCtx('');
|
||||
props.form.resetFields();
|
||||
onCancel && onCancel();
|
||||
}
|
||||
|
||||
// 编辑器内容变化时
|
||||
const handleContentChange = (content) => {
|
||||
console.log('编辑器内容', content);
|
||||
setCtx(content);
|
||||
try {
|
||||
// const _html = new QuillDeltaToHtmlConverter(content.ops, {}).convert();
|
||||
// props.form.setFieldsValue({'comment': _html.replace(/<\/?[^>]*>/g, '')});
|
||||
props.form.setFieldsValue({'comment': content});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
// 发送
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
props.form.validateFields((err, values) => {
|
||||
if (!err) {
|
||||
setShowQuill(false);
|
||||
const content = ctx;
|
||||
props.form.setFieldsValue({'comment': ''});
|
||||
setCtx('');
|
||||
// const _html = formatDelta(content.ops);
|
||||
// console.log('保存的内容=====》》》》', content);
|
||||
onSubmit && onSubmit(JSON.stringify(content));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const handleShowImage = (url) => {
|
||||
alert(url);
|
||||
}
|
||||
|
||||
// const _clazz = type === 'bottom' ? 'comment_form_bottom_area' : 'comment_form_area';
|
||||
let _clazz;
|
||||
if (type === 'bottom') {
|
||||
_clazz = showQuill ? 'comment_form_bottom_area active' : 'comment_form_bottom_area';
|
||||
} else {
|
||||
_clazz = 'comment_form_area';
|
||||
}
|
||||
return (
|
||||
<Form className={_clazz}>
|
||||
<FormItem>
|
||||
{
|
||||
getFieldDecorator('comment', {
|
||||
rules: [
|
||||
{ required: true, message: '评论内容不能为空'}
|
||||
],
|
||||
})(
|
||||
<Input
|
||||
onClick={() => handleInputClick(type)}
|
||||
placeholder="说点儿什么~"
|
||||
className={showQuill ? '' : 'show_input'}
|
||||
style={{
|
||||
height: showQuill ? '0px' : '40px',
|
||||
overflow: showQuill ? 'hidden' : 'auto',
|
||||
opacity: showQuill ? 0 : 1,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<QuillForEditor
|
||||
imgAttrs={{width: '60px', height: '30px'}}
|
||||
wrapStyle={{
|
||||
height: showQuill ? 'auto' : '0px',
|
||||
opacity: showQuill ? 1 : 0,
|
||||
overflow: showQuill ? 'none' : 'hidden',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
autoFocus={focus}
|
||||
style={{ height: '150px' }}
|
||||
placeholder="说点儿什么~"
|
||||
options={options}
|
||||
value={ctx}
|
||||
showUploadImage={handleShowImage}
|
||||
onContentChange={handleContentChange}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem style={{ textAlign: 'right', display: showQuill ? 'block' : 'none' }}>
|
||||
<Button onClick={handleCancle}>取消</Button>
|
||||
<Button onClick={handleSubmit} type="primary" style={{ marginLeft: '10px'}}>发送</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default Form.create()(CommentForm);
|
||||
42
public/react/src/common/components/comment/CommentIcon.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-18 10:49:46
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2019-12-25 10:03:21
|
||||
*/
|
||||
import './index.scss';
|
||||
import React from 'react';
|
||||
// import { Icon } from 'antd';
|
||||
// import MyIcon from '../MyIcon';
|
||||
function CommentIcon ({
|
||||
type, // 图标类型
|
||||
count, // 评论数
|
||||
iconClick,
|
||||
iconColor,
|
||||
theme,
|
||||
...props
|
||||
}) {
|
||||
|
||||
// 点击图标
|
||||
const handleSpanClick = () => {
|
||||
iconClick && iconClick();
|
||||
}
|
||||
|
||||
const _className = [undefined, null, ''].includes(count) ? 'comment_count_none' : 'comment_count';
|
||||
const _classIcon = `iconfont icon-${type} icon_font_size_14 comment_icon `;
|
||||
return (
|
||||
<span
|
||||
style={props.style}
|
||||
className={`comment_icon_count ${props.className}`}
|
||||
onClick={ handleSpanClick }
|
||||
>
|
||||
{/* <Icon className="comment_icon" type={type} style={{ color: iconColor }} theme={theme}/> */}
|
||||
<span className={_classIcon} style={{ color: iconColor }}></span>
|
||||
<span className={_className}>{ count }</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default CommentIcon;
|
||||
257
public/react/src/common/components/comment/CommentItem.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/*
|
||||
* @Description: 评论单列
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-17 17:35:17
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2019-12-27 11:05:17
|
||||
*/
|
||||
import './index.scss';
|
||||
import 'quill/dist/quill.core.css'; // 核心样式
|
||||
import 'quill/dist/quill.snow.css'; // 有工具栏
|
||||
import 'quill/dist/quill.bubble.css'; // 无工具栏
|
||||
import 'katex/dist/katex.min.css'; // katex 表达式样式
|
||||
import React, { useState } from 'react';
|
||||
import CommentIcon from './CommentIcon';
|
||||
import { getImageUrl, CNotificationHOC } from 'educoder'
|
||||
import { Icon } from 'antd';
|
||||
import CommentForm from './CommentForm';
|
||||
import QuillForEditor from '../../quillForEditor';
|
||||
|
||||
function CommentItem ({
|
||||
isAdmin,
|
||||
options,
|
||||
confirm,
|
||||
comment,
|
||||
submitDeleteComment,
|
||||
submitChildComment,
|
||||
likeComment,
|
||||
showOrHideComment
|
||||
}) {
|
||||
// 显示评论输入框
|
||||
const [showQuill, setShowQuill] = useState(false);
|
||||
// 加载更多评论内容
|
||||
// const [showMore, setShowMore] = useState(false);
|
||||
// 显示子列数
|
||||
const [showItemCount, setShowItemCount] = useState(5);
|
||||
// 箭头方向
|
||||
const [arrow, setArrow] = useState(false);
|
||||
// 上传图片的ulr
|
||||
const [url, setUrl] = useState('');
|
||||
|
||||
const {
|
||||
author = {}, // 作者
|
||||
id, // 评论id
|
||||
content, // 回复内容
|
||||
time, // 回复时间
|
||||
hidden, // 是否隐藏
|
||||
// hack_id, // OJ的ID
|
||||
praise_count, // 点赞数
|
||||
user_praise, // 当前用户是否点赞
|
||||
can_delete,
|
||||
children = [] // 子回复
|
||||
} = comment;
|
||||
|
||||
// 删除评论 type: parent | child, id
|
||||
const deleteComment = (id) => {
|
||||
confirm({
|
||||
title: '提示',
|
||||
content: ('确定要删除该条回复吗?'),
|
||||
onOk () {
|
||||
console.log('点击了删除', id);
|
||||
submitDeleteComment && submitDeleteComment(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 评论头像
|
||||
const commentAvatar = (author) => (
|
||||
<img
|
||||
className="item-flex flex-image"
|
||||
src={author.image_url ? getImageUrl(`images/${author.image_url}`) : 'https://b-ssl.duitang.com/uploads/item/201511/13/20151113110434_kyReJ.jpeg'}
|
||||
alt=""
|
||||
/>
|
||||
);
|
||||
|
||||
// 评论信息
|
||||
const commentInfo = (id, author, time, can_delete) => {
|
||||
const _classNames = can_delete ? 'item-close' : 'item-close hide';
|
||||
return (
|
||||
<div className="item-header">
|
||||
<span className="item-name">{author.name || ''}</span>
|
||||
<span className="item-time">{time || ''}</span>
|
||||
<span className={_classNames}>
|
||||
<span className="iconfont icon-shanchu icon_font_size_14" onClick={() => deleteComment(id)}></span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const handleShowUploadImage = (url) => {
|
||||
// console.log('==============>>>>>>>>>>>>',url);
|
||||
setUrl(url);
|
||||
}
|
||||
// 评论内容
|
||||
const commentCtx = (ctx) => {
|
||||
let _ctx = null;
|
||||
try {
|
||||
_ctx = JSON.parse(ctx);
|
||||
} catch (e) {
|
||||
_ctx = ctx;
|
||||
}
|
||||
return (
|
||||
<QuillForEditor
|
||||
readOnly={true}
|
||||
value={_ctx}
|
||||
showUploadImage={handleShowUploadImage}
|
||||
/>
|
||||
)};
|
||||
|
||||
// 加载更多
|
||||
const handleOnLoadMore = (len) => {
|
||||
setShowItemCount(!arrow ? len : 1);
|
||||
setArrow(!arrow);
|
||||
};
|
||||
|
||||
// 评论追加内容
|
||||
const commentAppend = (children = []) => {
|
||||
|
||||
const len = children.length;
|
||||
const _moreClass = len > showItemCount ? 'comment_item_loadmore show' : 'comment_item_loadmore'
|
||||
const lastTxt = len - showItemCount;
|
||||
const renderChild = (children) => {
|
||||
return children.map((child, i) => {
|
||||
const {
|
||||
id, // 评论id
|
||||
author = {},
|
||||
time,
|
||||
content,
|
||||
can_delete
|
||||
} = child;
|
||||
const showOrHide = i < showItemCount ? 'comment_item_show' : 'comment_item_hide';
|
||||
return (
|
||||
<li
|
||||
key={`child_${i}`}
|
||||
className={showOrHide}
|
||||
>
|
||||
<div className="comment_item_area comment_child_item_area">
|
||||
{commentAvatar(author)}
|
||||
<div className="item-flex item-desc">
|
||||
{commentInfo(id, author, time, can_delete)}
|
||||
{commentCtx(content)}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
const _clazz = len > 0 ? 'comment_item_append_list active' : 'comment_item_append_list';
|
||||
return (
|
||||
<ul className={_clazz}>
|
||||
{renderChild(children)}
|
||||
|
||||
<li className={_moreClass} onClick={() => handleOnLoadMore(len)}>
|
||||
<p className="loadmore-txt">展开其余{lastTxt}条评论</p>
|
||||
<p className="loadmore-icon">
|
||||
<Icon type={!arrow ? 'down' : 'up'}/>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
// 点击图标
|
||||
const handleShowOrHide = (id, hidden) => {
|
||||
showOrHideComment && showOrHideComment(id, hidden);
|
||||
}
|
||||
|
||||
// 点赞
|
||||
const handleClickLick = (id) => {
|
||||
likeComment && likeComment(id);
|
||||
}
|
||||
|
||||
// 点击评论icon
|
||||
const handleClickMessage = () => {
|
||||
setShowQuill(true);
|
||||
}
|
||||
|
||||
// 点击取消
|
||||
const handleClickCancel = () => {
|
||||
setShowQuill(false);
|
||||
}
|
||||
|
||||
// 点击保存
|
||||
const handleClickSubmit = (id) => {
|
||||
return (ctx) => {
|
||||
setShowQuill(false);
|
||||
submitChildComment && submitChildComment(id, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setUrl('');
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="comment_item_area">
|
||||
{commentAvatar(author)}
|
||||
<div className="item-flex item-desc">
|
||||
{commentInfo(id, author, time, can_delete)}
|
||||
{commentCtx(content)}
|
||||
|
||||
{commentAppend(children)}
|
||||
|
||||
<div className="comment_icon_area">
|
||||
<CommentIcon
|
||||
style={{ display: isAdmin ? 'inline-block' : 'none'}}
|
||||
className='comment-icon-margin'
|
||||
type={!hidden ? "xianshi" : 'yincang1'}
|
||||
iconClick={() => handleShowOrHide(id, !hidden ? 1 : 0)}
|
||||
/>
|
||||
|
||||
<CommentIcon
|
||||
style={{ display: can_delete ? 'inline-block' : 'none'}}
|
||||
className='comment-icon-margin'
|
||||
type={'shanchu'}
|
||||
iconClick={() => deleteComment(id)}
|
||||
/>
|
||||
{/* 回复 */}
|
||||
<CommentIcon
|
||||
className='comment-icon-margin'
|
||||
type="huifu1"
|
||||
count={children.length}
|
||||
iconClick={handleClickMessage}
|
||||
/>
|
||||
{/* 点赞 */}
|
||||
<CommentIcon
|
||||
iconColor={ user_praise ? '#5091FF' : '' }
|
||||
className='comment-icon-margin'
|
||||
theme={user_praise ? 'filled' : ''}
|
||||
type="dianzan"
|
||||
count={praise_count}
|
||||
iconClick={() => handleClickLick(id)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{ display: showQuill ? 'block' : 'none'}}
|
||||
className="comment_item_quill">
|
||||
<CommentForm
|
||||
onCancel={handleClickCancel}
|
||||
onSubmit={handleClickSubmit(id)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 显示上传的图片信息 */}
|
||||
<div className="show_upload_image" style={{ display: url ? 'block' : 'none'}}>
|
||||
<Icon type="close" className="image_close" onClick={handleClose}/>
|
||||
<div className="image_info">
|
||||
<img className="image" src={url} alt=""/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export default CNotificationHOC() (CommentItem);
|
||||
56
public/react/src/common/components/comment/CommentList.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* @Description: 评论列表页
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-17 17:34:00
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2019-12-24 18:08:07
|
||||
*/
|
||||
import './index.scss';
|
||||
import React from 'react';
|
||||
import CommentItem from './CommentItem';
|
||||
import { Empty } from 'antd';
|
||||
function CommentList (props) {
|
||||
const {
|
||||
isAdmin,
|
||||
commentLists, // 评论列表
|
||||
submitChildComment,
|
||||
submitDeleteComment,
|
||||
likeComment,
|
||||
showOrHideComment
|
||||
} = props;
|
||||
|
||||
const {comments = []} = commentLists;
|
||||
|
||||
const renderLi = () => {
|
||||
if (comments.length > 0) {
|
||||
return comments.map((item, index) => {
|
||||
return (
|
||||
<CommentItem
|
||||
isAdmin={isAdmin}
|
||||
key={`item_${index}`}
|
||||
submitChildComment={submitChildComment}
|
||||
submitDeleteComment={submitDeleteComment}
|
||||
comment={item}
|
||||
likeComment={likeComment}
|
||||
showOrHideComment={showOrHideComment}
|
||||
/>
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return (
|
||||
<div className="empty_comment">
|
||||
<Empty />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="comment_list_wrapper">
|
||||
{renderLi()}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default CommentList;
|
||||
46
public/react/src/common/components/comment/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* @Description: 评论组件
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-17 17:31:33
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2019-12-24 18:03:21
|
||||
*/
|
||||
import React from 'react';
|
||||
// import CommentForm from './CommentForm';
|
||||
import CommentList from './CommentList';
|
||||
function Comment (props) {
|
||||
|
||||
const {
|
||||
commentLists,
|
||||
// addComment,
|
||||
// cancelComment,
|
||||
isAdmin,
|
||||
addChildComment,
|
||||
likeComment,
|
||||
showOrHideComment,
|
||||
submitDeleteComment
|
||||
} = props;
|
||||
|
||||
// const handleCancelComment = () => {
|
||||
// cancelComment && cancelComment();
|
||||
// };
|
||||
return (
|
||||
<React.Fragment>
|
||||
{/* <CommentForm
|
||||
onCancel={handleCancelComment}
|
||||
onSubmit={addComment}
|
||||
/> */}
|
||||
<CommentList
|
||||
isAdmin={isAdmin}
|
||||
likeComment={likeComment}
|
||||
showOrHideComment={showOrHideComment}
|
||||
commentLists={commentLists}
|
||||
submitChildComment={addChildComment}
|
||||
submitDeleteComment={submitDeleteComment}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default Comment;
|
||||
230
public/react/src/common/components/comment/index.scss
Normal file
@@ -0,0 +1,230 @@
|
||||
$bdColor: rgba(244,244,244,1);
|
||||
$bgColor: rgba(250,250,250,1);
|
||||
$lh14: 14px;
|
||||
$lh22: 22px;
|
||||
$fz14: 14px;
|
||||
$fz12: 12px;
|
||||
$ml: 20px;
|
||||
|
||||
.comment_list_wrapper{
|
||||
box-sizing: border-box;
|
||||
// border-top: 1px solid $bdColor;
|
||||
.empty_comment{
|
||||
display: flex;
|
||||
height: calc(100vh - 200px);
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.comment_item_show{
|
||||
display: block;
|
||||
}
|
||||
.comment_item_hide{
|
||||
display: none;
|
||||
}
|
||||
.comment_item_area{
|
||||
display: flex;
|
||||
padding: 20px 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid $bdColor;
|
||||
|
||||
.comment_child_item_area:hover{
|
||||
.item-close{
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-image{
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.item-desc{
|
||||
flex: 1;
|
||||
// margin-left: $ml;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.item-header{
|
||||
font-size: $fz14;
|
||||
line-height: $lh14;
|
||||
color: #333;
|
||||
margin-left: 15px;
|
||||
.item-time{
|
||||
font-size: $fz12;
|
||||
line-height: $lh14;
|
||||
margin-left: $ml;
|
||||
}
|
||||
.item-close{
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.item-close.hide{
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
.item-ctx{
|
||||
position: relative;
|
||||
line-height: $lh22;
|
||||
font-size: $fz12;
|
||||
color: #333;
|
||||
margin-top: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.comment_icon_area{
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
|
||||
.comment-icon-margin{
|
||||
margin-left: 20px;
|
||||
}
|
||||
.comment-icon-margin-10{
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// .comment_item_quill{
|
||||
// // margin-top: 10px;
|
||||
// }
|
||||
.show_upload_image{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
&::before{
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width:100%;
|
||||
content: '';
|
||||
background: #000;
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.image_info{
|
||||
display: flex;
|
||||
position: absolute;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
left: 10%;
|
||||
top: 10%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// background: green;
|
||||
.image{
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.image_close{
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: 20px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.comment_icon_count{
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
|
||||
.comment_icon{
|
||||
color: #333;
|
||||
}
|
||||
.comment_count{
|
||||
color: #999999;
|
||||
margin-left: 10px;
|
||||
transition: color .3s;
|
||||
}
|
||||
.comment_count_none{
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
.comment_icon,
|
||||
.comment_count{
|
||||
color: #5091FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
.comment_item_append_list{
|
||||
display: none;
|
||||
position: relative;
|
||||
background-color: $bgColor;
|
||||
border-radius: 5px;
|
||||
padding: 0 15px 10px;
|
||||
margin: 15px 0;
|
||||
&.active{
|
||||
display: block;
|
||||
}
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
bottom: 100%;
|
||||
height: 0;
|
||||
width: 0;
|
||||
content: '';
|
||||
// border: 5px solid transparent;
|
||||
border: 10px solid transparent;
|
||||
border-bottom-color: $bgColor;
|
||||
}
|
||||
|
||||
.comment_item_loadmore{
|
||||
display: none;
|
||||
padding-top: 10px;
|
||||
cursor: pointer;
|
||||
.loadmore-txt,
|
||||
.loadmore-icon{
|
||||
color: #999;
|
||||
text-align: center;
|
||||
font-size: $fz12;
|
||||
}
|
||||
|
||||
&.show{
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon_font_size_14{
|
||||
font-size: 14px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.comment_form_area,
|
||||
.comment_form_bottom_area{
|
||||
width: 100%;
|
||||
}
|
||||
.comment_form_area{
|
||||
position: relative;
|
||||
background: #fff;
|
||||
// top: 10px;
|
||||
.ant-form-explain{
|
||||
padding-left: 0px;
|
||||
}
|
||||
.show_input{
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.comment_form_bottom_area{
|
||||
position: relative;
|
||||
background: #fff;
|
||||
top: 10px;
|
||||
|
||||
&.active{
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: -230px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
80
public/react/src/common/components/comment/util.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* @Description: quill delta -> html
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-24 08:51:25
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2019-12-26 09:30:11
|
||||
*/
|
||||
export const formatDelta = (deltas) => {
|
||||
|
||||
let formatted = [];
|
||||
|
||||
deltas.forEach(element => {
|
||||
let text = null;
|
||||
// 没有图片时
|
||||
if (!element['insert']['image']) {
|
||||
text = element['insert']; // 获取插入的内容
|
||||
// 元素有属性时
|
||||
if (element['attributes']) {
|
||||
// 获取所有的key值
|
||||
const keys = Object.keys(element['attributes']);
|
||||
keys.forEach(key => {
|
||||
text = operate(text, key, element['attributes'][key]);
|
||||
});
|
||||
} else if (element['insert']['formula']) {
|
||||
text = element['insert']['formula'];
|
||||
}
|
||||
} else {
|
||||
const image = element['insert']['image'];
|
||||
const {url, alt} = image;
|
||||
if (url && (url.startsWith('http') || url.startsWith('https'))) {
|
||||
text = `
|
||||
<img
|
||||
src="${url}"
|
||||
style="{display: 'inline-block'}"
|
||||
width="60px"
|
||||
height="30px"
|
||||
alt="${alt}"
|
||||
/>
|
||||
`;
|
||||
// text = "<img src="+url+" width='60px' height='30px' onclick='' alt="+alt+"/>";
|
||||
}
|
||||
}
|
||||
|
||||
formatted.push(text);
|
||||
});
|
||||
console.log(formatted);
|
||||
return formatted.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {*} text 文本内容
|
||||
* @param {*} key 属性key
|
||||
* @param {*} value 属性key对应的值
|
||||
*/
|
||||
export const operate = (text, key, value) => {
|
||||
let operatedText = null;
|
||||
debugger;
|
||||
switch (key) {
|
||||
case 'bold':
|
||||
operatedText = `<strong>${text}</strong>`;
|
||||
break;
|
||||
case 'italic':
|
||||
operatedText = `<i>${text}</i>`;
|
||||
break;
|
||||
case 'strike':
|
||||
operatedText = `<s>${text}</s>`;
|
||||
break;
|
||||
case 'underline':
|
||||
operatedText = `<u>${text}</u>`;
|
||||
break;
|
||||
case 'link':
|
||||
operatedText = `<a href="${value}" style="color: #5091ff; text-decoration: underline;" target="bland">${text}</a>`;
|
||||
break;
|
||||
default:
|
||||
operatedText = text;
|
||||
}
|
||||
|
||||
return operatedText;
|
||||
}
|
||||
1775
public/react/src/common/components/form/City.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React,{ Component } from "react";
|
||||
import { getImageUrl } from 'educoder'
|
||||
class PopInstruction extends Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state={
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
setVisible = (visible) => {
|
||||
this.setState({ visible })
|
||||
}
|
||||
render(){
|
||||
const { visible } = this.state
|
||||
const { width, id } = this.props
|
||||
let _width = width || 92
|
||||
return(
|
||||
<div style={{position: 'relative', display: 'inline' }}>
|
||||
{/*
|
||||
width: ${width || 292}px!important;
|
||||
*/}
|
||||
<style>{`
|
||||
#repository_url_tip {
|
||||
top: 32px!important;
|
||||
left: 10px!important;
|
||||
z-index: 999;
|
||||
}
|
||||
.repository_url_tippostion {
|
||||
position: absolute;
|
||||
}
|
||||
#repository_url_tip.popInstruction${id} {
|
||||
width: ${_width}px !important;
|
||||
}
|
||||
|
||||
.top-black-trangleft {
|
||||
display: block;
|
||||
border-width: 8px;
|
||||
position: absolute;
|
||||
top: -16px;
|
||||
border-style: dashed solid dashed dashed;
|
||||
border-color: transparent transparent rgba(5,16,26,.6);
|
||||
font-size: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
`}</style>
|
||||
<a className="ml10" onClick={()=>this.setVisible(!visible)}>
|
||||
<img src={getImageUrl("images/educoder/problem.png")}
|
||||
style={{marginBottom: '2px'}}
|
||||
/>
|
||||
</a>
|
||||
<div className={`invite-tip popInstruction${id} clearfix repository_url_tippostion`} style={{display: visible===true?"block":"none"}}
|
||||
id="repository_url_tip"
|
||||
>
|
||||
<span className="top-black-trangleft"></span>
|
||||
<div className="padding20 invitecontent clearfix">
|
||||
{this.props.children}
|
||||
</div>
|
||||
<p className="inviteTipbtn with100">
|
||||
<a onClick={()=>this.setVisible(false)} >
|
||||
知道了
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default PopInstruction;
|
||||
12
public/react/src/common/components/markdown/DMDEditor.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.hideMd {
|
||||
display: none !important;
|
||||
}
|
||||
.content_editorMd_show {
|
||||
height: auto;
|
||||
min-height: 38px;
|
||||
width: 800px;
|
||||
border: 1px solid rgba(234,234,234,1);
|
||||
margin-top: 2px;
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
83
public/react/src/common/components/markdown/DMDEditor.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import React,{ Component } from "react";
|
||||
import TPMMDEditor from '../../../modules/tpm/challengesnew/TPMMDEditor'
|
||||
import {markdownToHTML} from 'educoder'
|
||||
import './DMDEditor.css'
|
||||
// 需要父组件通过toShowMode、toMDMode 来控制,一次只能打开一个DMDEditor
|
||||
class DMDEditor extends Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.mdRef = React.createRef()
|
||||
this.state={
|
||||
mdMode: false,
|
||||
// value: this.props.initValue
|
||||
}
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
|
||||
}
|
||||
componentDidMount() {
|
||||
// if(this.props.initValue != this.mdRef.current.getValue()) {
|
||||
// this.mdRef.current.setValue(this.props.initValue)
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
toMDMode = () => {
|
||||
this.setState({mdMode: true}, () => {
|
||||
this.mdRef.current.resize()
|
||||
this.mdRef.current.setValue(this.props.initValue)
|
||||
})
|
||||
this.props.toMDMode(this)
|
||||
}
|
||||
toShowMode = () => {
|
||||
this.setState({mdMode: false})
|
||||
this.props.toShowMode && this.props.toShowMode(this)
|
||||
}
|
||||
onCMBlur = () => {
|
||||
this.toShowMode()
|
||||
}
|
||||
onChange = (val) => {
|
||||
// this.setState({ value: val })
|
||||
this.props.onChange(val)
|
||||
if (this.state.showError == true) {
|
||||
this.setState({showError: false})
|
||||
}
|
||||
}
|
||||
showError = () => {
|
||||
this.mdRef.current.showError()
|
||||
this.setState({showError: true})
|
||||
}
|
||||
render(){
|
||||
const { mdMode, showError } = this.state;
|
||||
const { initValue } = this.props;
|
||||
let _style = {}
|
||||
if (showError) {
|
||||
_style.border = '1px solid red'
|
||||
}
|
||||
_style = Object.assign(_style, {display: mdMode == true ? 'none' : '', color: initValue? '': '#999', alignItems: 'center', wordBreak: 'break-all'})
|
||||
return(
|
||||
<React.Fragment>
|
||||
<style>{`
|
||||
|
||||
`}</style>
|
||||
<div id="content_editorMd_show" className="new_li content_editorMd_show markdown-body"
|
||||
style={_style}
|
||||
dangerouslySetInnerHTML={{__html: initValue ? markdownToHTML(initValue):this.props.placeholder}}
|
||||
onClick={this.toMDMode}
|
||||
>
|
||||
|
||||
</div>
|
||||
{/*
|
||||
onCMBlur={this.onCMBlur} */}
|
||||
<TPMMDEditor
|
||||
ref={this.mdRef}
|
||||
{...this.props}
|
||||
initValue={initValue}
|
||||
className={`${this.props.className} ${mdMode == true ? '' : 'hideMd'}`}
|
||||
onChange={this.onChange}
|
||||
></TPMMDEditor>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default DMDEditor;
|
||||
@@ -0,0 +1,6 @@
|
||||
.markdownToHtml.editormd-html-preview, .markdownToHtml.editormd-preview-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.markdownToHtml.editormd-html-preview p.editormd-tex, .markdownToHtml.editormd-preview-container p.editormd-tex {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React,{ Component } from "react";
|
||||
import { markdownToHTML } from 'educoder'
|
||||
import './MarkdownToHtml.css'
|
||||
/**
|
||||
selector 需要传入唯一的selector作为id,不然会引起冲突
|
||||
*/
|
||||
class MarkdownToHtml extends Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state={
|
||||
}
|
||||
}
|
||||
_markdownToHTML = (content, selector) => {
|
||||
markdownToHTML(content, selector)
|
||||
}
|
||||
componentDidUpdate = (prevProps) => {
|
||||
if (this.props.content) {
|
||||
if ( prevProps.content != this.props.content ) {
|
||||
this._markdownToHTML(this.props.content, `markdown_to_html_${this.props.selector || ''}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
componentDidMount () {
|
||||
this.props.content && this._markdownToHTML(this.props.content, `markdown_to_html_${this.props.selector || ''}`)
|
||||
|
||||
}
|
||||
render(){
|
||||
const { style, className } = this.props
|
||||
let _selector = `markdown_to_html_${this.props.selector || ''}`
|
||||
|
||||
return(
|
||||
<div id={_selector } className={`markdownToHtml new_li markdown-body ${className} ${_selector}`}
|
||||
// dangerouslySetInnerHTML={{__html: markdownToHTML(this.props.content)}}
|
||||
style={style}
|
||||
>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default MarkdownToHtml;
|
||||
237
public/react/src/common/components/media/AliyunUploader.js
Normal file
@@ -0,0 +1,237 @@
|
||||
import React, { useState, useEffect, memo } from 'react';
|
||||
|
||||
import { getUrl2, isDev } from 'educoder'
|
||||
import axios from 'axios'
|
||||
const $ = window.$
|
||||
let _url_origin = getUrl2()
|
||||
let _path = isDev() ? 'public' : 'build'
|
||||
|
||||
let uploader
|
||||
|
||||
let _testHost = '' ; '192.168.2.63:3001/api'
|
||||
|
||||
|
||||
const login = 'innov'
|
||||
|
||||
function createUploader () {
|
||||
uploader = new window.AliyunUpload.Vod({
|
||||
timeout: $('#timeout').val() || 60000,
|
||||
partSize: $('#partSize').val() || 1048576,
|
||||
parallel: $('#parallel').val() || 5,
|
||||
retryCount: $('#retryCount').val() || 3,
|
||||
retryDuration: $('#retryDuration').val() || 2,
|
||||
region: $('#region').val() || 'ap-southeast-1',
|
||||
userId: $('#userId').val() || 1829848226361863, //, // 1303984639806000,
|
||||
// 添加文件成功
|
||||
addFileSuccess: function (uploadInfo) {
|
||||
console.log('addFileSuccess')
|
||||
$('#authUpload').attr('disabled', false)
|
||||
$('#resumeUpload').attr('disabled', false)
|
||||
$('#status').text('添加文件成功, 等待上传...')
|
||||
console.log("addFileSuccess: " + uploadInfo.file.name)
|
||||
|
||||
$('#pauseUpload').attr('disabled', false)
|
||||
uploader.startUpload()
|
||||
},
|
||||
// 开始上传
|
||||
onUploadstarted: function (uploadInfo) {
|
||||
// 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法
|
||||
// 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值,调用点播的不同接口获取uploadauth和uploadAddress
|
||||
// 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
|
||||
// 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi
|
||||
// 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
|
||||
// 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
|
||||
if (!uploadInfo.videoId) {
|
||||
// var createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6'
|
||||
// $.get(createUrl, function (data) {
|
||||
// var uploadAuth = data.UploadAuth
|
||||
// var uploadAddress = data.UploadAddress
|
||||
// var videoId = data.VideoId
|
||||
// uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId)
|
||||
// }, 'json')
|
||||
|
||||
var createUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
|
||||
|
||||
axios.post(createUrl, {
|
||||
title: 'testvod1',
|
||||
file_name: 'aa.mp4'
|
||||
}).then((response) => {
|
||||
// if (response.data.status == )
|
||||
const data = response.data.data
|
||||
var uploadAuth = data.UploadAuth
|
||||
var uploadAddress = data.UploadAddress
|
||||
var videoId = data.VideoId
|
||||
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
|
||||
|
||||
$('#status').text('文件开始上传...')
|
||||
console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
|
||||
} else {
|
||||
// 如果videoId有值,根据videoId刷新上传凭证
|
||||
// https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY
|
||||
// var refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
|
||||
// $.get(refreshUrl, function (data) {
|
||||
// var uploadAuth = data.UploadAuth
|
||||
// var uploadAddress = data.UploadAddress
|
||||
// var videoId = data.VideoId
|
||||
// uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId)
|
||||
// }, 'json')
|
||||
|
||||
var refreshUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
|
||||
|
||||
axios.put(refreshUrl, {
|
||||
video_id: uploadInfo.videoId,
|
||||
}).then((response) => {
|
||||
const data = response.data.data
|
||||
var uploadAuth = data.UploadAuth
|
||||
var uploadAddress = data.UploadAddress
|
||||
var videoId = data.VideoId
|
||||
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
},
|
||||
// 文件上传成功
|
||||
onUploadSucceed: function (uploadInfo) {
|
||||
console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
|
||||
$('#status').text('文件上传成功!')
|
||||
},
|
||||
// 文件上传失败
|
||||
onUploadFailed: function (uploadInfo, code, message) {
|
||||
console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message)
|
||||
$('#status').text('文件上传失败!')
|
||||
},
|
||||
// 取消文件上传
|
||||
onUploadCanceled: function (uploadInfo, code, message) {
|
||||
console.log("Canceled file: " + uploadInfo.file.name + ", code: " + code + ", message:" + message)
|
||||
$('#status').text('文件上传已暂停!')
|
||||
},
|
||||
// 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
|
||||
onUploadProgress: function (uploadInfo, totalSize, progress) {
|
||||
console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%")
|
||||
var progressPercent = Math.ceil(progress * 100)
|
||||
$('#auth-progress').text(progressPercent)
|
||||
$('#status').text('文件上传中...')
|
||||
},
|
||||
// 上传凭证超时
|
||||
onUploadTokenExpired: function (uploadInfo) {
|
||||
// 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时
|
||||
// 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth
|
||||
// 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth
|
||||
$('#status').text('文件上传超时!')
|
||||
|
||||
// let refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
|
||||
// $.get(refreshUrl, function (data) {
|
||||
// var uploadAuth = data.UploadAuth
|
||||
// uploader.resumeUploadWithAuth(uploadAuth)
|
||||
// console.log('upload expired and resume upload with uploadauth ' + uploadAuth)
|
||||
// }, 'json')
|
||||
|
||||
var refreshUrl = `${_testHost}/users/${login}/video_auths.json?debug=true`
|
||||
|
||||
axios.put(refreshUrl, {
|
||||
video_id: uploadInfo.videoId,
|
||||
}).then((response) => {
|
||||
const data = response.data.data
|
||||
var uploadAuth = data.UploadAuth
|
||||
uploader.resumeUploadWithAuth(uploadAuth)
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
},
|
||||
// 全部文件上传结束
|
||||
onUploadEnd: function (uploadInfo) {
|
||||
$('#status').text('文件上传完毕!')
|
||||
console.log("onUploadEnd: uploaded all the files")
|
||||
}
|
||||
})
|
||||
return uploader
|
||||
}
|
||||
function AliyunUploader(props) {
|
||||
|
||||
useEffect(() => {
|
||||
if (window.AliyunUpload && window.AliyunUpload.Vod) {
|
||||
|
||||
} else {
|
||||
$.getScript(
|
||||
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/es6-promise.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
$.getScript(
|
||||
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/aliyun-oss-sdk-5.3.1.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
$.getScript(
|
||||
`${_url_origin}/react/${_path}/js/aliyun-upload/aliyun-upload-sdk-1.5.0.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#fileUpload').on('change', function (e) {
|
||||
var file = e.target.files[0]
|
||||
if (!file) {
|
||||
alert("请先选择需要上传的文件!")
|
||||
return
|
||||
}
|
||||
var Title = file.name
|
||||
var userData = '{"Vod":{}}'
|
||||
if (uploader) {
|
||||
uploader.stopUpload()
|
||||
$('#auth-progress').text('0')
|
||||
$('#status').text("")
|
||||
}
|
||||
if (!uploader) {
|
||||
uploader = createUploader()
|
||||
}
|
||||
// 首先调用 uploader.addFile(event.target.files[i], null, null, null, userData)
|
||||
console.log(uploader)
|
||||
let result = uploader.addFile(file, null, null, null, userData)
|
||||
$('#authUpload').attr('disabled', false)
|
||||
// $('#pauseUpload').attr('disabled', true)
|
||||
// $('#resumeUpload').attr('disabled', true)
|
||||
})
|
||||
return () => {
|
||||
$('#fileUpload').off('change')
|
||||
}
|
||||
}, [])
|
||||
|
||||
let { source, id, className, type } = props;
|
||||
|
||||
function onStop() {
|
||||
$('#resumeUpload').attr('disabled', false)
|
||||
// $('#pauseUpload').attr('disabled', true)
|
||||
uploader.stopUpload()
|
||||
}
|
||||
function onResume() {
|
||||
// $('#resumeUpload').attr('disabled', true)
|
||||
// $('#pauseUpload').attr('disabled', false)
|
||||
uploader.startUpload()
|
||||
}
|
||||
return(
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<input type="file" id="fileUpload"></input>
|
||||
<label class="status">上传状态: <span id="status"></span></label>
|
||||
</div>
|
||||
<div class="upload-type">
|
||||
上传方式一, 使用 UploadAuth 上传:
|
||||
{/* <button id="authUpload" disabled="true">开始上传</button>
|
||||
<button id="pauseUpload" disabled="true" onClick={onStop}>暂停</button>
|
||||
<button id="resumeUpload" disabled="true" onClick={onResume}>恢复上传</button> */}
|
||||
<button id="authUpload" >开始上传</button>
|
||||
<button id="pauseUpload" onClick={onStop}>暂停</button>
|
||||
<button id="resumeUpload" onClick={onResume}>恢复上传</button>
|
||||
<span class="progress">上传进度: <i id="auth-progress">0</i> %</span>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
}
|
||||
export default AliyunUploader
|
||||
188
public/react/src/common/components/media/AliyunUploaderDemo.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import React, { useState, useEffect, memo } from 'react';
|
||||
|
||||
import { getUrl2, isDev } from 'educoder'
|
||||
|
||||
const $ = window.$
|
||||
let _url_origin = getUrl2()
|
||||
let _path = _isDev ? 'public' : 'build'
|
||||
|
||||
let uploader
|
||||
|
||||
function createUploader () {
|
||||
uploader = new AliyunUpload.Vod({
|
||||
timeout: $('#timeout').val() || 60000,
|
||||
partSize: $('#partSize').val() || 1048576,
|
||||
parallel: $('#parallel').val() || 5,
|
||||
retryCount: $('#retryCount').val() || 3,
|
||||
retryDuration: $('#retryDuration').val() || 2,
|
||||
region: $('#region').val() || 'ap-southeast-1',
|
||||
userId: $('#userId').val() || 1202060945918292, // 1303984639806000,
|
||||
// 添加文件成功
|
||||
addFileSuccess: function (uploadInfo) {
|
||||
console.log('addFileSuccess')
|
||||
$('#authUpload').attr('disabled', false)
|
||||
$('#resumeUpload').attr('disabled', false)
|
||||
$('#status').text('添加文件成功, 等待上传...')
|
||||
console.log("addFileSuccess: " + uploadInfo.file.name)
|
||||
|
||||
$('#pauseUpload').attr('disabled', false)
|
||||
uploader.startUpload()
|
||||
},
|
||||
// 开始上传
|
||||
onUploadstarted: function (uploadInfo) {
|
||||
// 如果是 UploadAuth 上传方式, 需要调用 uploader.setUploadAuthAndAddress 方法
|
||||
// 如果是 UploadAuth 上传方式, 需要根据 uploadInfo.videoId是否有值,调用点播的不同接口获取uploadauth和uploadAddress
|
||||
// 如果 uploadInfo.videoId 有值,调用刷新视频上传凭证接口,否则调用创建视频上传凭证接口
|
||||
// 注意: 这里是测试 demo 所以直接调用了获取 UploadAuth 的测试接口, 用户在使用时需要判断 uploadInfo.videoId 存在与否从而调用 openApi
|
||||
// 如果 uploadInfo.videoId 存在, 调用 刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)
|
||||
// 如果 uploadInfo.videoId 不存在,调用 获取视频上传地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
|
||||
if (!uploadInfo.videoId) {
|
||||
var createUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/CreateUploadVideo?Title=testvod1&FileName=aa.mp4&BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&VideoId=5bfcc7864fc14b96972842172207c9e6'
|
||||
$.get(createUrl, function (data) {
|
||||
var uploadAuth = data.UploadAuth
|
||||
var uploadAddress = data.UploadAddress
|
||||
var videoId = data.VideoId
|
||||
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId)
|
||||
}, 'json')
|
||||
$('#status').text('文件开始上传...')
|
||||
console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
|
||||
} else {
|
||||
// 如果videoId有值,根据videoId刷新上传凭证
|
||||
// https://help.aliyun.com/document_detail/55408.html?spm=a2c4g.11186623.6.630.BoYYcY
|
||||
var refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
|
||||
$.get(refreshUrl, function (data) {
|
||||
var uploadAuth = data.UploadAuth
|
||||
var uploadAddress = data.UploadAddress
|
||||
var videoId = data.VideoId
|
||||
uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId)
|
||||
}, 'json')
|
||||
}
|
||||
},
|
||||
// 文件上传成功
|
||||
onUploadSucceed: function (uploadInfo) {
|
||||
console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
|
||||
$('#status').text('文件上传成功!')
|
||||
},
|
||||
// 文件上传失败
|
||||
onUploadFailed: function (uploadInfo, code, message) {
|
||||
console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message)
|
||||
$('#status').text('文件上传失败!')
|
||||
},
|
||||
// 取消文件上传
|
||||
onUploadCanceled: function (uploadInfo, code, message) {
|
||||
console.log("Canceled file: " + uploadInfo.file.name + ", code: " + code + ", message:" + message)
|
||||
$('#status').text('文件上传已暂停!')
|
||||
},
|
||||
// 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
|
||||
onUploadProgress: function (uploadInfo, totalSize, progress) {
|
||||
console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%")
|
||||
var progressPercent = Math.ceil(progress * 100)
|
||||
$('#auth-progress').text(progressPercent)
|
||||
$('#status').text('文件上传中...')
|
||||
},
|
||||
// 上传凭证超时
|
||||
onUploadTokenExpired: function (uploadInfo) {
|
||||
// 上传大文件超时, 如果是上传方式一即根据 UploadAuth 上传时
|
||||
// 需要根据 uploadInfo.videoId 调用刷新视频上传凭证接口(https://help.aliyun.com/document_detail/55408.html)重新获取 UploadAuth
|
||||
// 然后调用 resumeUploadWithAuth 方法, 这里是测试接口, 所以我直接获取了 UploadAuth
|
||||
$('#status').text('文件上传超时!')
|
||||
|
||||
let refreshUrl = 'https://demo-vod.cn-shanghai.aliyuncs.com/voddemo/RefreshUploadVideo?BusinessType=vodai&TerminalType=pc&DeviceModel=iPhone9,2&UUID=59ECA-4193-4695-94DD-7E1247288&AppVersion=1.0.0&Title=haha1&FileName=xxx.mp4&VideoId=' + uploadInfo.videoId
|
||||
$.get(refreshUrl, function (data) {
|
||||
var uploadAuth = data.UploadAuth
|
||||
uploader.resumeUploadWithAuth(uploadAuth)
|
||||
console.log('upload expired and resume upload with uploadauth ' + uploadAuth)
|
||||
}, 'json')
|
||||
},
|
||||
// 全部文件上传结束
|
||||
onUploadEnd: function (uploadInfo) {
|
||||
$('#status').text('文件上传完毕!')
|
||||
console.log("onUploadEnd: uploaded all the files")
|
||||
}
|
||||
})
|
||||
return uploader
|
||||
}
|
||||
function AliyunUploaderDemo(props) {
|
||||
|
||||
useEffect(() => {
|
||||
if (window.AliyunUpload && window.AliyunUpload.Vod) {
|
||||
|
||||
} else {
|
||||
$.getScript(
|
||||
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/es6-promise.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
$.getScript(
|
||||
`${_url_origin}/react/${_path}/js/aliyun-upload/lib/aliyun-oss-sdk-5.3.1.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
$.getScript(
|
||||
`${_url_origin}/react/${_path}/js/aliyun-upload/aliyun-upload-sdk-1.5.0.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('#fileUpload').on('change', function (e) {
|
||||
var file = e.target.files[0]
|
||||
if (!file) {
|
||||
alert("请先选择需要上传的文件!")
|
||||
return
|
||||
}
|
||||
var Title = file.name
|
||||
var userData = '{"Vod":{}}'
|
||||
if (uploader) {
|
||||
uploader.stopUpload()
|
||||
$('#auth-progress').text('0')
|
||||
$('#status').text("")
|
||||
}
|
||||
if (!uploader) {
|
||||
uploader = createUploader()
|
||||
}
|
||||
// 首先调用 uploader.addFile(event.target.files[i], null, null, null, userData)
|
||||
console.log(uploader)
|
||||
uploader.addFile(file, null, null, null, userData)
|
||||
$('#authUpload').attr('disabled', false)
|
||||
// $('#pauseUpload').attr('disabled', true)
|
||||
// $('#resumeUpload').attr('disabled', true)
|
||||
})
|
||||
// return () => {
|
||||
|
||||
// }
|
||||
}, [])
|
||||
|
||||
let { source, id, className, type } = props;
|
||||
|
||||
function onStop() {
|
||||
$('#resumeUpload').attr('disabled', false)
|
||||
// $('#pauseUpload').attr('disabled', true)
|
||||
uploader.stopUpload()
|
||||
}
|
||||
function onResume() {
|
||||
// $('#resumeUpload').attr('disabled', true)
|
||||
// $('#pauseUpload').attr('disabled', false)
|
||||
uploader.startUpload()
|
||||
}
|
||||
return(
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<input type="file" id="fileUpload"></input>
|
||||
<label class="status">上传状态: <span id="status"></span></label>
|
||||
</div>
|
||||
<div class="upload-type">
|
||||
上传方式一, 使用 UploadAuth 上传:
|
||||
{/* <button id="authUpload" disabled="true">开始上传</button>
|
||||
<button id="pauseUpload" disabled="true" onClick={onStop}>暂停</button>
|
||||
<button id="resumeUpload" disabled="true" onClick={onResume}>恢复上传</button> */}
|
||||
<button id="authUpload" >开始上传</button>
|
||||
<button id="pauseUpload" onClick={onStop}>暂停</button>
|
||||
<button id="resumeUpload" onClick={onResume}>恢复上传</button>
|
||||
<span class="progress">上传进度: <i id="auth-progress">0</i> %</span>
|
||||
<span></span>
|
||||
</div>
|
||||
|
||||
</React.Fragment>
|
||||
)
|
||||
|
||||
}
|
||||
export default AliyunUploaderDemo
|
||||
110
public/react/src/common/components/media/Clappr.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import React,{ Component } from "react";
|
||||
import { getUrl2 } from "educoder";
|
||||
import ReactPlayer from 'react-player'
|
||||
|
||||
const $ = window.$
|
||||
let _url_origin = getUrl2()
|
||||
|
||||
class Clappr extends Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state={
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this['player'+this.props.id] && this['player'+this.props.id].destroy()
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
return;
|
||||
|
||||
const source = this.props.source || "http://your.video/here.mp4"
|
||||
const { id, type } = this.props
|
||||
const _id = `#_player${id}`
|
||||
|
||||
if (!window['Clappr'] && window['ClapprLoading'] == true) {
|
||||
setTimeout(() => {
|
||||
this.componentDidMount()
|
||||
}, 300)
|
||||
return;
|
||||
}
|
||||
// && window['clappr-playback-rate-plugin']
|
||||
if (window['Clappr'] ) {
|
||||
// https://github.com/clappr/clappr/issues/1839
|
||||
// http://clappr.github.io/classes/Player.html#method_mute
|
||||
this['player'+id] = new window.Clappr.Player({
|
||||
source: source, parentId: _id,
|
||||
height: type == 'mp3' ? 60 : 360,
|
||||
hideMediaControl: type == 'mp3' ? false : true,
|
||||
// plugins: {
|
||||
// 'core': [window.Clappr.MediaControl, window['clappr-playback-rate-plugin'].default]
|
||||
// }
|
||||
});
|
||||
} else {
|
||||
window['ClapprLoading'] = true;
|
||||
$.getScript(
|
||||
`${_url_origin}/javascripts/media/clappr.min.js`,
|
||||
(data, textStatus, jqxhr) => {
|
||||
window.clappr = window.Clappr
|
||||
// $.getScript(
|
||||
// `${_url_origin}/javascripts/media/clappr-playback-rate-plugin.min.js`,
|
||||
// (data, textStatus, jqxhr) => {
|
||||
this['player'+id] = new window.Clappr.Player({
|
||||
source: source, parentId: _id,
|
||||
height: type == 'mp3' ? 60 : 360,
|
||||
hideMediaControl: type == 'mp3' ? false : true,
|
||||
// plugins: {
|
||||
// 'core': [window.Clappr.MediaControl, window['clappr-playback-rate-plugin'].default]
|
||||
// }
|
||||
});
|
||||
|
||||
// })
|
||||
|
||||
});
|
||||
|
||||
//
|
||||
// $.when(
|
||||
// $.getScript( `${_url_origin}/javascripts/media/clappr.min.js` ),
|
||||
// // $.getScript( `${_url_origin}/javascripts/media/clappr-thumbnails-plugin.js` ),
|
||||
// $.getScript( `${_url_origin}/javascripts/media/clappr-playback-rate-plugin.min.js` ),
|
||||
// $.Deferred(function( deferred ){
|
||||
// $( deferred.resolve );
|
||||
// })
|
||||
// ).done(function(){
|
||||
// //place your code here, the scripts are all loaded
|
||||
// const player = new window.Clappr.Player({
|
||||
// source: source, parentId: _id,
|
||||
// plugins: {
|
||||
// 'core': [window.Clappr.MediaControl, window.Clappr.Playback]
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
|
||||
let { source, id, className, type } = this.props;
|
||||
const _id = `_player${id}`
|
||||
return(
|
||||
<React.Fragment>
|
||||
{/* https://github.com/CookPete/react-player/issues/686 */}
|
||||
<ReactPlayer url={source} playing={false} controls={true} width={400} height={ type == 'mp3' ? 55 : 290}/>
|
||||
|
||||
{/* <style>{`
|
||||
.playback_rate {
|
||||
margin-right: 16px;
|
||||
}
|
||||
`}</style>
|
||||
<div id={_id} className={className + ' ' + type}></div> */}
|
||||
|
||||
{/* 原生 */}
|
||||
{/* { type == 'mp3' ? <audio src={source} preload controls></audio>
|
||||
: <video src={source} controls="controls">
|
||||
您的浏览器不支持 video 标签。
|
||||
</video>} */}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default Clappr;
|
||||
20
public/react/src/common/context/ThemeContext.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
|
||||
export const themes = {
|
||||
light: {
|
||||
foreground: '#000000',
|
||||
background: '#eeeeee',
|
||||
foreground_select: '#4CACFF',
|
||||
foreground_orange1: '#FF6800',
|
||||
foreground_tip: '#333',
|
||||
|
||||
},
|
||||
dark: {
|
||||
foreground: '#ffffff',
|
||||
background: '#222222',
|
||||
},
|
||||
};
|
||||
|
||||
export const ThemeContext = React.createContext(
|
||||
themes.light // default value
|
||||
);
|
||||
29
public/react/src/common/course/ActionBtn.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React, { Component } from 'react';
|
||||
import {Link} from 'react-router-dom'
|
||||
|
||||
const map={"blue":"blueFull","greyBack":"greyBack","grey":"greyWidthFixed","green":"greenBack",'greyLine':"greyLine",'orangeLine':"orangeLine",
|
||||
'colorBlue': 'colorBlue', // 蓝字白底
|
||||
}
|
||||
class ActionBtn extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let{to, children, style, className, ...others}=this.props
|
||||
return(
|
||||
<React.Fragment>
|
||||
{
|
||||
to==undefined ?
|
||||
<a href="javascript:void(0)" onClick={this.props.onClick}
|
||||
{...others}
|
||||
className={"Actionbtn "+`${map[style || 'blue']} ${this.props.className}`}
|
||||
>{children}</a>
|
||||
:
|
||||
<Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`} {...others}>{this.props.children}</Link>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default ActionBtn;
|
||||
31
public/react/src/common/course/WordsBtn.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { Component } from 'react';
|
||||
import {Link} from 'react-router-dom'
|
||||
|
||||
const map={"blue":"colorblue","white":"colorwhite","grey":"colorgrey", 'orange': "color-orange",'colorgrey9':'color-grey-9'}
|
||||
class WordsBtn extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let{to, href,targets, style2, style, className, ...others }=this.props
|
||||
return(
|
||||
<React.Fragment>
|
||||
{
|
||||
to==undefined&&targets==undefined ?
|
||||
<a href={href || "javascript:void(0)"} onClick={this.props.onClick} className={"btn "+`${map[this.props.style]} ${this.props.className}`}
|
||||
style={style2} {...others}
|
||||
>{this.props.children}</a>:
|
||||
targets!=undefined? <a href={to} target="_blank" className={"btn "+`${map[this.props.style]} ${this.props.className}`}
|
||||
style={style2} {...others}
|
||||
>{this.props.children}</a>
|
||||
:
|
||||
<Link to={to} className={"btn "+`${map[this.props.style]} ${this.props.className}`}
|
||||
style={style2} {...others}
|
||||
>{this.props.children}</Link>
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default WordsBtn;
|
||||
77
public/react/src/common/educoder.js
Normal file
@@ -0,0 +1,77 @@
|
||||
//import { from } from '_array-flatten@2.1.2@array-flatten';
|
||||
|
||||
// export { default as OrderStateUtil } from '../routes/Order/components/OrderStateUtil';
|
||||
|
||||
export { getImageUrl as getImageUrl,getmyUrl as getmyUrl, getRandomNumber as getRandomNumber,getUrl as getUrl, publicSearchs as publicSearchs,getRandomcode as getRandomcode,getUrlmys as getUrlmys, getUrl2 as getUrl2, setImagesUrl as setImagesUrl
|
||||
, getUploadActionUrl as getUploadActionUrl,getUploadActionUrltwo as getUploadActionUrltwo ,getUploadActionUrlthree as getUploadActionUrlthree, getUploadActionUrlOfAuth as getUploadActionUrlOfAuth
|
||||
, getTaskUrlById as getTaskUrlById, TEST_HOST ,htmlEncode as htmlEncode ,getupload_git_file as getupload_git_file} from './UrlTool';
|
||||
|
||||
export {setmiyah as setmiyah} from './Component';
|
||||
export { default as queryString } from './UrlTool2';
|
||||
|
||||
export { SnackbarHOC as SnackbarHOC } from './SnackbarHOC';
|
||||
|
||||
export { trigger as trigger, on as on, off as off
|
||||
, broadcastChannelPostMessage, broadcastChannelOnmessage } from './EventUtil';
|
||||
|
||||
export { updatePageParams as updatePageParams } from './RouterUtil';
|
||||
|
||||
export { bytesToSize as bytesToSize } from './UnitUtil';
|
||||
|
||||
export { markdownToHTML, uploadNameSizeSeperator, appendFileSizeToUploadFile, appendFileSizeToUploadFileAll, isImageExtension,
|
||||
downloadFile, sortDirections } from './TextUtil'
|
||||
export { handleDateString, getNextHalfHourOfMoment,formatDuring } from './DateUtil'
|
||||
|
||||
export { configShareForIndex, configShareForPaths, configShareForShixuns, configShareForCourses, configShareForCustom } from './util/ShareUtil'
|
||||
|
||||
export { isDev as isDev, isMobile } from './Env'
|
||||
|
||||
export { toStore as toStore, fromStore as fromStore } from './Store'
|
||||
|
||||
export { trace_collapse, trace, debug, info, warn, error, trace_c, debug_c, info_c, warn_c, error_c } from './LogUtil'
|
||||
|
||||
export { EDU_ADMIN, EDU_BUSINESS, EDU_SHIXUN_MANAGER, EDU_SHIXUN_MEMBER, EDU_CERTIFICATION_TEACHER
|
||||
, EDU_GAME_MANAGER, EDU_TEACHER, EDU_NORMAL} from './Const'
|
||||
|
||||
|
||||
export { default as AttachmentList } from './components/attachment/AttachmentList'
|
||||
|
||||
export { themes, ThemeContext } from './context/ThemeContext'
|
||||
|
||||
export { ModalHOC } from './components/ModalHOC'
|
||||
|
||||
export { SetAppModel } from './components/SetAppModel'
|
||||
|
||||
export { default as LinkAfterLogin } from './components/LinkAfterLogin'
|
||||
export { default as Cropper } from './components/Cropper'
|
||||
export { default as ConditionToolTip } from './components/ConditionToolTip'
|
||||
// export { default as DragValidator } from './components/DragValidator'
|
||||
|
||||
export { default as PopInstruction } from './components/instruction/PopInstruction'
|
||||
|
||||
export { default as City } from './components/form/City'
|
||||
|
||||
|
||||
// course
|
||||
export { default as WordsBtn } from './course/WordsBtn'
|
||||
|
||||
export { default as ActionBtn } from './course/ActionBtn'
|
||||
|
||||
export { default as MarkdownToHtml } from './components/markdown/MarkdownToHtml'
|
||||
|
||||
export { default as DMDEditor } from './components/markdown/DMDEditor'
|
||||
|
||||
export { default as Clappr } from './components/media/Clappr'
|
||||
export { default as AliyunUploader } from './components/media/AliyunUploader'
|
||||
|
||||
|
||||
export { default as ImageLayer2 } from './hooks/ImageLayer2'
|
||||
|
||||
// 外部
|
||||
export { default as CBreadcrumb } from '../modules/courses/common/CBreadcrumb'
|
||||
export { CNotificationHOC as CNotificationHOC } from '../modules/courses/common/CNotificationHOC'
|
||||
export { default as ModalWrapper } from '../modules/courses/common/ModalWrapper'
|
||||
export { default as NoneData } from '../modules/courses/coursesPublic/NoneData'
|
||||
|
||||
export {default as WordNumberTextarea} from '../modules/modals/WordNumberTextarea'
|
||||
|
||||
48
public/react/src/common/hooks/ImageLayer2.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React, { useState, useEffect, memo } from 'react';
|
||||
import ImageLayer from '../../modules/page/layers/ImageLayer';
|
||||
import { isImageExtension } from 'educoder';
|
||||
const $ = window.$;
|
||||
function ImageLayer2(props) {
|
||||
const [showImage, setShowImage] = useState(false)
|
||||
const [imageSrc, setImageSrc] = useState('')
|
||||
|
||||
const { parentSel, childSel, watchPropsArray } = props
|
||||
|
||||
const onImageLayerClose = () => {
|
||||
setShowImage(false)
|
||||
setImageSrc('')
|
||||
}
|
||||
const onDelegateClick = (event) => {
|
||||
const imageSrc = event.target.src || event.target.getAttribute('src') || event.target.getAttribute('href')
|
||||
// 判断imageSrc是否是图片
|
||||
const fileName = event.target.innerHTML.trim()
|
||||
if (isImageExtension(imageSrc.trim()) || isImageExtension(fileName) || event.target.tagName == 'IMG' || imageSrc.indexOf('base64,') != -1) {
|
||||
// 非回复里的头像图片; 非emoticons
|
||||
if (imageSrc.indexOf('/images/avatars/User') === -1 &&
|
||||
imageSrc.indexOf('kindeditor/plugins/emoticons') === -1 ) {
|
||||
setShowImage(true)
|
||||
setImageSrc(imageSrc)
|
||||
}
|
||||
event.stopPropagation()
|
||||
event.preventDefault && event.preventDefault()
|
||||
event.originalEvent.preventDefault()
|
||||
// event.originalEvent.stopPropagation()
|
||||
// event.originalEvent.cancelBubble = true
|
||||
return false;
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
$(parentSel)
|
||||
.delegate(childSel, "click", onDelegateClick);
|
||||
|
||||
return () => {
|
||||
$(parentSel).undelegate(childSel, "click", onDelegateClick )
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<ImageLayer showImage={showImage} imageSrc={imageSrc} onImageLayerClose={onImageLayerClose}></ImageLayer>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(ImageLayer2)
|
||||
35
public/react/src/common/quillForEditor/FillBlot.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* @Description: 填空
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2020-01-06 09:02:29
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2020-02-05 10:44:01
|
||||
*/
|
||||
import Quill from 'quill';
|
||||
let Inline = Quill.import('blots/inline');
|
||||
// const BlockEmbed = Quill.import('blots/embed');
|
||||
class FillBlot extends Inline {
|
||||
static create (value) {
|
||||
const node = super.cerate(value);
|
||||
// node.classList.add('icon icon-bianji2');
|
||||
// node.setAttribute('data-fill', 'fill');
|
||||
console.log('编辑器值===》》》》》', value);
|
||||
node.setAttribute('data_index', value.data_index);
|
||||
node.nodeValue = value.text;
|
||||
return node;
|
||||
}
|
||||
|
||||
static value (node) {
|
||||
return {
|
||||
// dataSet: node.getAttribute('data-fill'),
|
||||
data_index: node.getAttribute('data_index')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FillBlot.blotName = "fill";
|
||||
FillBlot.tagName = "span";
|
||||
|
||||
export default FillBlot;
|
||||
70
public/react/src/common/quillForEditor/ImageBlot.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* @Description: 重写图片
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-16 15:50:45
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2019-12-31 13:59:02
|
||||
*/
|
||||
import Quill from "quill";
|
||||
|
||||
const BlockEmbed = Quill.import('blots/block/embed');
|
||||
|
||||
export default class ImageBlot extends BlockEmbed {
|
||||
|
||||
static create(value) {
|
||||
|
||||
const node = super.create();
|
||||
node.setAttribute('alt', value.alt);
|
||||
node.setAttribute('src', value.url);
|
||||
// console.log('~~~~~~~~~~~', node, value);
|
||||
node.addEventListener('click', function () {
|
||||
value.onclick(value.url);
|
||||
}, false);
|
||||
if (value.width) {
|
||||
node.setAttribute('width', value.width);
|
||||
}
|
||||
if (value.height) {
|
||||
node.setAttribute('height', value.height);
|
||||
}
|
||||
|
||||
if (value.id) {
|
||||
node.setAttribute('id', value.id);
|
||||
}
|
||||
// 宽度和高度都不存在时,
|
||||
if (!value.width && !value.height) {
|
||||
// node.setAttribute('display', 'block');
|
||||
node.setAttribute('width', '100%');
|
||||
}
|
||||
|
||||
// node.setAttribute('style', { cursor: 'pointer' });
|
||||
|
||||
// if (node.onclick) {
|
||||
// console.log('image 有图片点击事件======》》》》》》');
|
||||
// // node.setAttribute('onclick', node.onCLick);
|
||||
// }
|
||||
// 给图片添加点击事件
|
||||
// node.onclick = () => {
|
||||
// value.onClick && value.onClick(value.url);
|
||||
// }
|
||||
return node;
|
||||
}
|
||||
|
||||
// 获取节点值
|
||||
static value (node) {
|
||||
|
||||
return {
|
||||
alt: node.getAttribute('alt'),
|
||||
url: node.getAttribute('src'),
|
||||
onclick: node.onclick,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
display: node.getAttribute('display'),
|
||||
id: node.id,
|
||||
// style: node.style
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ImageBlot.blotName = 'image';
|
||||
ImageBlot.tagName = 'img';
|
||||
95
public/react/src/common/quillForEditor/README.md
Normal file
@@ -0,0 +1,95 @@
|
||||
<!--
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2020-01-06 16:20:03
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2020-01-09 09:45:29
|
||||
-->
|
||||
## QuillForEditor 使用 [https://quilljs.com/]
|
||||
|
||||
### 导入
|
||||
|
||||
- 目录 src/common/quillForEditor (默认加载当前文件夹下的 index.js 文件)
|
||||
|
||||
### 参数
|
||||
|
||||
| 字段 | 描述 |
|
||||
| ----- | ----- |
|
||||
| placeholder | 提示信息 |
|
||||
| readOnly | 只读(只读取模式时,没有 工具栏且内容不可编辑,通常用于展示quill内容) |
|
||||
| autoFocus | 自动获得焦点 |
|
||||
| options | 配置参数, 指定工具栏内容 |
|
||||
| value | 文本编辑器内容 |
|
||||
| imgAttrs | 指定上传图片的尺寸 { width: 'xxpx}, height: 'xxpx'|
|
||||
| style | 指定quill容器样式 |
|
||||
| wrapStyle | 指定包裹quill容器的样式|
|
||||
| onContentChange | 当编辑器内容变化时调用此回调函数(注: 此时返回的内容为对象,提交到后台时需要格式成 JSON 字符串: JSON.stringify(xx)) |
|
||||
| showUploadImage | 点击放大上传成功后的图片, 返回上传成功后的图片 url, (评论时点击图片这么大)|
|
||||
|
||||
|
||||
|
||||
### 添加工具栏
|
||||
|
||||
- 默认所有的
|
||||
|
||||
```
|
||||
const options = [
|
||||
'bold', // 加粗
|
||||
'italic', // 斜体
|
||||
'underline', // 下划线
|
||||
{size: ['12px', '14px', '16px', '18px', '20px']}, // 字体大小
|
||||
{align: []}, // 对齐方式
|
||||
{list: 'ordered'}, // 有序列表
|
||||
{list: 'bullet'}, // 无序列表
|
||||
{script: 'sub'}, // 下标 x2
|
||||
{script: 'super'}, // 上标 平方 (x2)
|
||||
{ 'color': [] }, // 字体颜色
|
||||
{ 'background': [] }, // 背景色
|
||||
{header: [1,2,3,4,5,false]}, // H1,H2 ...
|
||||
'blockquote', // 文件左边加一个边框样式
|
||||
'code-block', // 块内容
|
||||
'link', // 链接
|
||||
'image', // 图片
|
||||
'video', // 视频
|
||||
'formula', // 数学公式
|
||||
'clean' // 清除
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
### 使用
|
||||
|
||||
````
|
||||
编辑模式是放不大图片的
|
||||
import QuillForEditor from 'xxx';
|
||||
|
||||
// 指定需要显示的工具栏信息, 不指定加载全部
|
||||
const options = [
|
||||
|
||||
];
|
||||
|
||||
/**
|
||||
* @description 获取编辑器返回的内容
|
||||
* @params [Object] value 编辑器内容
|
||||
*/
|
||||
const handleCtxChange = (value) => {
|
||||
// 编辑器内容非空判断
|
||||
const _text = quill.getText();
|
||||
const reg = /^[\s\S]*.*[^\s][\s\S]*$/;
|
||||
if (!reg.test(_text)) {
|
||||
// 处理编辑器内容为空
|
||||
} else {
|
||||
// 提交到后台的内容需要处理一下;
|
||||
value = JSON.stringify(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
<QuillForEditor
|
||||
options={options}
|
||||
onContentChange={handleCtxChange}
|
||||
>
|
||||
</QuillForEditor>
|
||||
````
|
||||
|
||||
47
public/react/src/common/quillForEditor/deepEqual.js
Normal file
@@ -0,0 +1,47 @@
|
||||
function deepEqual (prev, current) {
|
||||
if (prev === current) { // 基本类型比较,值,类型都相同 或者同为 null or undefined
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!prev && current)
|
||||
|| (prev && !current)
|
||||
|| (!prev && !current)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(prev)) {
|
||||
if (!Array.isArray(current)) return false;
|
||||
if (prev.length !== current.length) return false;
|
||||
|
||||
for (let i = 0; i < prev.length; i++) {
|
||||
if (!deepEqual(current[i], prev[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof current === 'object') {
|
||||
if (typeof prev !== 'object') return false;
|
||||
const prevKeys = Object.keys(prev);
|
||||
const curKeys = Object.keys(current);
|
||||
|
||||
if (prevKeys.length !== curKeys.length) return false;
|
||||
|
||||
prevKeys.sort();
|
||||
curKeys.sort();
|
||||
|
||||
for (let i = 0; i < prevKeys.length; i++) {
|
||||
if (prevKeys[i] !== curKeys[i]) return false;
|
||||
const key = prevKeys[i];
|
||||
if (!deepEqual(prev[key], current[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default deepEqual;
|
||||
280
public/react/src/common/quillForEditor/index.js
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* @Description: quill 编辑器
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-18 08:49:30
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2020-02-05 11:23:03
|
||||
*/
|
||||
import './index.scss';
|
||||
import 'quill/dist/quill.core.css'; // 核心样式
|
||||
import 'quill/dist/quill.snow.css'; // 有工具栏
|
||||
import 'quill/dist/quill.bubble.css'; // 无工具栏
|
||||
import 'katex/dist/katex.min.css'; // katex 表达式样式
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import Quill from 'quill';
|
||||
import katex from 'katex';
|
||||
import deepEqual from './deepEqual.js'
|
||||
import { fetchUploadImage } from '../../services/ojService.js';
|
||||
import { getImageUrl } from 'educoder'
|
||||
import ImageBlot from './ImageBlot';
|
||||
import FillBlot from './FillBlot';
|
||||
const Size = Quill.import('attributors/style/size');
|
||||
const Font = Quill.import('formats/font');
|
||||
// const Color = Quill.import('attributes/style/color');
|
||||
Size.whitelist = ['12px', '14px', '16px', '18px', '20px', false];
|
||||
Font.whitelist = ['SimSun', 'SimHei','Microsoft-YaHei','KaiTi','FangSong','Arial','Times-New-Roman','sans-serif'];
|
||||
|
||||
window.Quill = Quill;
|
||||
window.katex = katex;
|
||||
Quill.register(ImageBlot);
|
||||
Quill.register(Size);
|
||||
Quill.register(Font, true);
|
||||
// Quill.register({'modules/toolbar': Toolbar});
|
||||
Quill.register({
|
||||
'formats/fill': FillBlot
|
||||
});
|
||||
// Quill.register(Color);
|
||||
|
||||
|
||||
function QuillForEditor ({
|
||||
placeholder,
|
||||
readOnly,
|
||||
autoFocus = false,
|
||||
options,
|
||||
value,
|
||||
imgAttrs = {}, // 指定图片的宽高
|
||||
style = {},
|
||||
wrapStyle = {},
|
||||
showUploadImage,
|
||||
onContentChange,
|
||||
addFill, // 点击填空成功的回调
|
||||
deleteFill // 删除填空,返回删除的下标
|
||||
// getQuillContent
|
||||
}) {
|
||||
// toolbar 默认值
|
||||
const defaultConfig = [
|
||||
'bold', 'italic', 'underline',
|
||||
{size: ['12px', '14px', '16px', '18px', '20px']},
|
||||
{align: []}, {list: 'ordered'}, {list: 'bullet'}, // 列表
|
||||
{script: 'sub'}, {script: 'super'},
|
||||
{ 'color': [] }, { 'background': [] },
|
||||
{header: [1,2,3,4,5,false]},
|
||||
'blockquote', 'code-block',
|
||||
'link', 'image', 'video',
|
||||
'formula',
|
||||
'clean'
|
||||
];
|
||||
|
||||
const editorRef = useRef(null);
|
||||
// quill 实例
|
||||
const [quill, setQuill] = useState(null);
|
||||
const [selection, setSelection] = useState(null);
|
||||
const [fillCount, setFillCount] = useState(0);
|
||||
const [quillCtx, setQuillCtx] = useState({});
|
||||
|
||||
// 文本内容变化时
|
||||
const handleOnChange = content => {
|
||||
// getQuillContent && getQuillContent(quill);
|
||||
onContentChange && onContentChange(content, quill);
|
||||
};
|
||||
|
||||
const renderOptions = options || defaultConfig;
|
||||
|
||||
const bindings = {
|
||||
tab: {
|
||||
key: 9,
|
||||
handler: function () {
|
||||
console.log('调用了tab=====>>>>');
|
||||
}
|
||||
},
|
||||
backspace: {
|
||||
key: 'Backspace',
|
||||
/**
|
||||
* @param {*} range
|
||||
* { index, // 删除元素的位置
|
||||
* length // 删除元素的个数, 当删除一个时, length=0, 其它等于删除的元素的个数
|
||||
* }
|
||||
* @param {*} context 上下文
|
||||
*/
|
||||
handler: function (range, context) {
|
||||
/**
|
||||
* index: 删除元素的位置
|
||||
* length: 删除元素的个数
|
||||
*/
|
||||
const {index, length} = range;
|
||||
const _start = length === 0 ? index - 1 : index;
|
||||
const _length = length || 1;
|
||||
let delCtx = this.quill.getText(_start, _length); // 删除的元素
|
||||
// aa
|
||||
const reg = /▁/g;
|
||||
const delArrs = delCtx.match(reg);
|
||||
if (delArrs) {
|
||||
const r = window.confirm('确定要删除吗?');
|
||||
if (r) {
|
||||
let leaveCtx; // 获取删除元素之前的内容
|
||||
if (length === 0) {
|
||||
leaveCtx = this.quill.getText(0, index - 1);
|
||||
} else {
|
||||
leaveCtx = this.quill.getText(0, index);
|
||||
}
|
||||
const leaveArrs = leaveCtx.match(reg);
|
||||
const leaveLen = (leaveArrs || []).length;
|
||||
let delIndexs = [];
|
||||
// 获取删除元素的下标
|
||||
delArrs.forEach((item, i) => {
|
||||
leaveLen === 0 ? delIndexs.push(i) : delIndexs.push(leaveLen + i);
|
||||
});
|
||||
deleteFill && deleteFill(delIndexs); // 调用删除回调, 返回删除的元素下标[]
|
||||
return true
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
// quill 配置信息
|
||||
const quillOption = {
|
||||
modules: {
|
||||
toolbar: renderOptions,
|
||||
keyboard: {
|
||||
bindings: bindings
|
||||
}
|
||||
// toolbar: {
|
||||
// container: renderOptions
|
||||
// }
|
||||
},
|
||||
readOnly,
|
||||
placeholder,
|
||||
theme: readOnly ? 'bubble' : 'snow',
|
||||
};
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const quillNode = document.createElement('div');
|
||||
editorRef.current.appendChild(quillNode);
|
||||
const _quill = new Quill(editorRef.current, quillOption);
|
||||
|
||||
setQuill(_quill);
|
||||
// 处理图片上传功能
|
||||
_quill.getModule('toolbar').addHandler('image', (e) => {
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.setAttribute('accept', 'image/*');
|
||||
input.click();
|
||||
|
||||
input.onchange = async (e) => {
|
||||
const file = input.files[0]; // 获取文件信息
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const range = _quill.getSelection(true);
|
||||
let fileUrl = ''; // 保存上传成功后图片的url
|
||||
// 上传文件
|
||||
const result = await fetchUploadImage(formData);
|
||||
// 获取上传图片的url
|
||||
if (result.data && result.data.id) {
|
||||
fileUrl = getImageUrl(`api/attachments/${result.data.id}`);
|
||||
}
|
||||
// 根据id获取文件路径
|
||||
const { width, height } = imgAttrs;
|
||||
// console.log('上传图片的url:', fileUrl);
|
||||
if (fileUrl) {
|
||||
_quill.insertEmbed(range.index, 'image', {
|
||||
url: fileUrl,
|
||||
alt: '图片信息',
|
||||
onClick: showUploadImage,
|
||||
width,
|
||||
height
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// 处理填空
|
||||
_quill.getModule('toolbar').addHandler('fill', (e) => {
|
||||
// alert(1111);
|
||||
setFillCount(fillCount + 1);
|
||||
const range = _quill.getSelection(true);
|
||||
_quill.insertText(range.index, '▁');
|
||||
addFill && addFill(); // 调用添加回调
|
||||
});
|
||||
}, []);
|
||||
|
||||
// 设置值
|
||||
useEffect(() => {
|
||||
if (!quill) return
|
||||
|
||||
const previous = quill.getContents()
|
||||
|
||||
if (value && value.hasOwnProperty('ops')) {
|
||||
// console.log(value.ops);
|
||||
const ops = value.ops || [];
|
||||
ops.forEach((item, i) => {
|
||||
if (item.insert['image']) {
|
||||
item.insert['image'] = Object.assign({}, item.insert['image'], {style: { cursor: 'pointer' }, onclick: (url) => showUploadImage(url)});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const current = value
|
||||
if (!deepEqual(previous, current)) {
|
||||
setSelection(quill.getSelection())
|
||||
if (typeof value === 'string' && value) {
|
||||
// debugger
|
||||
quill.clipboard.dangerouslyPasteHTML(value, 'api');
|
||||
if (autoFocus) {
|
||||
quill.focus();
|
||||
} else {
|
||||
quill.blur();
|
||||
}
|
||||
} else {
|
||||
quill.setContents(value)
|
||||
if (autoFocus) quill.focus();
|
||||
}
|
||||
}
|
||||
}, [quill, value, setQuill, autoFocus]);
|
||||
|
||||
// 清除选择区域
|
||||
useEffect(() => {
|
||||
if (quill && selection) {
|
||||
quill.setSelection(selection)
|
||||
setSelection(null)
|
||||
}
|
||||
}, [quill, selection, setSelection]);
|
||||
|
||||
// 设置placeholder值
|
||||
useEffect(() => {
|
||||
if (!quill || !quill.root) return;
|
||||
quill.root.dataset.placeholder = placeholder;
|
||||
}, [quill, placeholder]);
|
||||
|
||||
// 处理内容变化
|
||||
useEffect(() => {
|
||||
if (!quill) return;
|
||||
if (typeof handleOnChange !== 'function') return;
|
||||
let handler;
|
||||
quill.on(
|
||||
'text-change',
|
||||
(handler = (delta, oldDelta, source) => {
|
||||
const _ctx = quill.getContents();
|
||||
setQuillCtx(_ctx);
|
||||
handleOnChange(quill.getContents()); // getContents: 检索编辑器内容
|
||||
})
|
||||
);
|
||||
return () => {
|
||||
quill.off('text-change', handler);
|
||||
}
|
||||
}, [quill, handleOnChange]);
|
||||
|
||||
// 返回结果
|
||||
return (
|
||||
<div className='quill_editor_for_react_area' style={wrapStyle}>
|
||||
<div ref={editorRef} style={style}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default QuillForEditor;
|
||||
132
public/react/src/common/quillForEditor/index.scss
Normal file
@@ -0,0 +1,132 @@
|
||||
.quill_editor_for_react_area{
|
||||
// background: #fff;
|
||||
// margin: 0 15px;
|
||||
.ql-editing{
|
||||
left: 0 !important;
|
||||
}
|
||||
.ql-editor{
|
||||
img{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="12px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="12px"]::before {
|
||||
content: '12px';
|
||||
font-size: 12px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="14px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="14px"]::before {
|
||||
content: '14px';
|
||||
font-size: 14px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="16px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="16px"]::before {
|
||||
content: '16px';
|
||||
font-size: 16px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="18px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="18px"]::before {
|
||||
content: '18px';
|
||||
font-size: 18px;
|
||||
}
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="20px"]::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="20px"]::before {
|
||||
content: '20px';
|
||||
font-size: 20px;
|
||||
}
|
||||
//默认的样式
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
|
||||
content: '14px';
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimSun]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimSun]::before {
|
||||
content: "宋体";
|
||||
font-family: "SimSun";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=SimHei]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=SimHei]::before {
|
||||
content: "黑体";
|
||||
font-family: "SimHei";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Microsoft-YaHei]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Microsoft-YaHei]::before {
|
||||
content: "微软雅黑";
|
||||
font-family: "Microsoft YaHei";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=KaiTi]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=KaiTi]::before {
|
||||
content: "楷体";
|
||||
font-family: "KaiTi";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=FangSong]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=FangSong]::before {
|
||||
content: "仿宋";
|
||||
font-family: "FangSong";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Arial]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Arial]::before {
|
||||
content: "Arial";
|
||||
font-family: "Arial";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=Times-New-Roman]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=Times-New-Roman]::before {
|
||||
content: "Times New Roman";
|
||||
font-family: "Times New Roman";
|
||||
}
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value=sans-serif]::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value=sans-serif]::before {
|
||||
content: "sans-serif";
|
||||
font-family: "sans-serif";
|
||||
}
|
||||
|
||||
.ql-font-SimSun {
|
||||
font-family: "SimSun";
|
||||
}
|
||||
.ql-font-SimHei {
|
||||
font-family: "SimHei";
|
||||
}
|
||||
.ql-font-Microsoft-YaHei {
|
||||
font-family: "Microsoft YaHei";
|
||||
}
|
||||
.ql-font-KaiTi {
|
||||
font-family: "KaiTi";
|
||||
}
|
||||
.ql-font-FangSong {
|
||||
font-family: "FangSong";
|
||||
}
|
||||
.ql-font-Arial {
|
||||
font-family: "Arial";
|
||||
}
|
||||
.ql-font-Times-New-Roman {
|
||||
font-family: "Times New Roman";
|
||||
}
|
||||
.ql-font-sans-serif {
|
||||
font-family: "sans-serif";
|
||||
}
|
||||
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
|
||||
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
|
||||
content: "微软雅黑";
|
||||
font-family: "Microsoft YaHei";
|
||||
}
|
||||
|
||||
// 填空图标
|
||||
.ql-snow .ql-fill{
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: #05101A;
|
||||
// font-size: 18px;
|
||||
vertical-align: top;
|
||||
&::before{
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: -1px;
|
||||
content: '\e709';
|
||||
font-family: 'iconfont';
|
||||
margin-left: -7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
public/react/src/common/reactQuill/ImageBlot.js
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* @Description: 重写图片
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-16 15:50:45
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-17 16:44:48
|
||||
*/
|
||||
import Quill from "quill";
|
||||
|
||||
const BlockEmbed = Quill.import('blots/block/embed');
|
||||
|
||||
export default class ImageBlot extends BlockEmbed {
|
||||
|
||||
static create(value) {
|
||||
|
||||
const node = super.create();
|
||||
|
||||
node.setAttribute('alt', value.alt);
|
||||
node.setAttribute('src', value.url);
|
||||
|
||||
if (value.width) {
|
||||
node.setAttribute('width', value.width);
|
||||
}
|
||||
if (value.height) {
|
||||
node.setAttribute('height', value.height);
|
||||
}
|
||||
// 宽度和高度都不存在时,
|
||||
if (!value.width && !value.height) {
|
||||
node.setAttribute('display', 'block');
|
||||
node.setAttribute('width', '100%');
|
||||
}
|
||||
// 给图片添加点击事件
|
||||
node.onclick = () => {
|
||||
value.onClick && value.onClick(value.url);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static value (node) {
|
||||
|
||||
return {
|
||||
alt: node.getAttribute('alt'),
|
||||
url: node.getAttribute('src'),
|
||||
onclick: node.onclick,
|
||||
// width: node.width,
|
||||
// height: node.height,
|
||||
display: node.getAttribute('display')
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
ImageBlot.blotName = 'image';
|
||||
ImageBlot.tagName = 'img';
|
||||
45
public/react/src/common/reactQuill/ReactQuill.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-09 09:09:42
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-18 08:46:20
|
||||
*/
|
||||
import 'quill/dist/quill.core.css'; // 核心样式
|
||||
import 'quill/dist/quill.snow.css'; // 有工具栏
|
||||
import 'quill/dist/quill.bubble.css'; // 无工具栏
|
||||
import 'katex/dist/katex.min.css'; // katex 表达式样式
|
||||
import React, { useState, useReducer, useEffect } from 'react';
|
||||
import useQuill from './useQuill';
|
||||
|
||||
function ReactQuill ({
|
||||
disallowColors, // 不可见时颜色
|
||||
placeholder, // 提示信息
|
||||
uploadImage, // 图片上传
|
||||
onChange, // 内容变化时
|
||||
options, // 配置信息
|
||||
value, // 显示的内容
|
||||
style,
|
||||
showUploadImage // 显示上传图片
|
||||
}) {
|
||||
|
||||
const [element, setElement] = useState(); // quill 渲染节点
|
||||
|
||||
useQuill({
|
||||
disallowColors,
|
||||
placeholder,
|
||||
uploadImage,
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
showUploadImage,
|
||||
element
|
||||
});
|
||||
|
||||
return (
|
||||
<div className='react_quill_area' ref={setElement} style={style}/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ReactQuill;
|
||||
47
public/react/src/common/reactQuill/deepEqual.js
Normal file
@@ -0,0 +1,47 @@
|
||||
function deepEqual (prev, current) {
|
||||
if (prev === current) { // 基本类型比较,值,类型都相同 或者同为 null or undefined
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((!prev && current)
|
||||
|| (prev && !current)
|
||||
|| (!prev && !current)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(prev)) {
|
||||
if (!Array.isArray(current)) return false;
|
||||
if (prev.length !== current.length) return false;
|
||||
|
||||
for (let i = 0; i < prev.length; i++) {
|
||||
if (!deepEqual(current[i], prev[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof current === 'object') {
|
||||
if (typeof prev !== 'object') return false;
|
||||
const prevKeys = Object.keys(prev);
|
||||
const curKeys = Object.keys(current);
|
||||
|
||||
if (prevKeys.length !== curKeys.length) return false;
|
||||
|
||||
prevKeys.sort();
|
||||
curKeys.sort();
|
||||
|
||||
for (let i = 0; i < prevKeys.length; i++) {
|
||||
if (prevKeys[i] !== curKeys[i]) return false;
|
||||
const key = prevKeys[i];
|
||||
if (!deepEqual(prev[key], current[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export default deepEqual;
|
||||
26
public/react/src/common/reactQuill/flatten.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* @Description: 将多维数组转变成一维数组
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-09 09:35:01
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-16 11:36:22
|
||||
*/
|
||||
function flatten (array) {
|
||||
return flatten.rec(array, []);
|
||||
}
|
||||
|
||||
flatten.rec = function flatten (array, result) {
|
||||
|
||||
for (let item of array) {
|
||||
if (Array.isArray(item)) {
|
||||
flatten(item, result);
|
||||
} else {
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default flatten;
|
||||
108
public/react/src/common/reactQuill/index.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* @Description: 入口文件
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-17 10:41:48
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-17 20:34:40
|
||||
*/
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import ReactQuill from './lib';
|
||||
|
||||
function Wrapper (props) {
|
||||
// 默认工具栏配置项
|
||||
const toolbarConfig = [
|
||||
['bold', 'italic', 'underline'],
|
||||
[{align: []}, {list: 'ordered'}, {list: 'bullet'}], // 列表
|
||||
[{script: 'sub'}, {script: 'super'}],
|
||||
[{header: [1,2,3,4,5,false]}],
|
||||
['blockquote', 'code-block'],
|
||||
['link', 'image', 'video'],
|
||||
['formula'],
|
||||
['clean']
|
||||
];
|
||||
|
||||
const [placeholder] = useState(props.placeholder || 'placeholder');
|
||||
const [disableBold] = useState(false);
|
||||
const [value, setValue] = useState(props.value || '');
|
||||
const [toolbar, setToolbar] = useState(toolbarConfig);
|
||||
const [theme, setTheme] = useState(props.theme || 'snow');
|
||||
const [readOnly] = useState(props.readOnly || false);
|
||||
|
||||
const {
|
||||
onContentChagne, // 当编辑器内容变化时调用该函数
|
||||
showUploadImage, // 显示上传图片, 返回url,主要用于点击图片放大
|
||||
} = props;
|
||||
|
||||
// 配置信息
|
||||
const options = {
|
||||
modules: {
|
||||
toolbar: toolbar,
|
||||
clipboard: {
|
||||
matchVisual: false
|
||||
}
|
||||
},
|
||||
readOnly: readOnly,
|
||||
theme: theme
|
||||
}
|
||||
// 配置信息
|
||||
useEffect (() => {
|
||||
if (props.options) {
|
||||
setToolbar(props.options);
|
||||
}
|
||||
setTheme(props.theme || 'snow');
|
||||
setValue(props.value);
|
||||
}, [props]);
|
||||
|
||||
// 当内容变化时
|
||||
const handleOnChange = useCallback(
|
||||
contents => {
|
||||
if (disableBold) {
|
||||
setValue({
|
||||
ops: contents.ops.map(x => {
|
||||
x = {...x};
|
||||
if (x && x.attributes && x.attributes.bold) {
|
||||
x.attributes = { ...x.attributes };
|
||||
delete x.attributes.bold;
|
||||
if (!Object.keys(x.attributes).length) {
|
||||
delete x.attributes;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
})
|
||||
});
|
||||
} else {
|
||||
setValue(contents);
|
||||
}
|
||||
onContentChagne && onContentChagne(contents);
|
||||
}, [disableBold]
|
||||
);
|
||||
|
||||
// 图片上传
|
||||
const handleUploadImage = (files) => {
|
||||
console.log('选择的图片信息', files);
|
||||
}
|
||||
|
||||
// 显示图片
|
||||
const handleShowUploadImage = (url) => {
|
||||
// console.log('上传的图片url:', url);
|
||||
showUploadImage && showUploadImage(url);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ReactQuill
|
||||
value={value}
|
||||
style={props.style}
|
||||
onChange={handleOnChange}
|
||||
placeholder={`${placeholder}`}
|
||||
options={options}
|
||||
uploadImage={handleUploadImage}
|
||||
showUploadImage={(url) => handleShowUploadImage(url)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default Wrapper;
|
||||
// ReactDOM.render(<Wrapper />, document.querySelector('#root'));
|
||||
32
public/react/src/common/reactQuill/index.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
#quill-toolbar{
|
||||
.quill-btn{
|
||||
vertical-align: middle;
|
||||
}
|
||||
.quill_image{
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
width: 28px;
|
||||
height: 24px;
|
||||
overflow: hidden;
|
||||
.image_input{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
.ql-image{
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.react_quill_area{
|
||||
.ql-toolbar:not(:last-child) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
13
public/react/src/common/reactQuill/lib.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* @Description: 导出 ReactQuill
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-09 09:08:24
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-16 11:37:13
|
||||
*/
|
||||
import ReactQuill from './ReactQuill';
|
||||
import useQuill from './useQuill';
|
||||
|
||||
export default ReactQuill;
|
||||
export { useQuill };
|
||||
27
public/react/src/common/reactQuill/useDeepEqualMemo.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-12 19:48:55
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-16 11:38:16
|
||||
*/
|
||||
import { useState, useEffect } from 'react';
|
||||
import deepEqual from './deepEqual';
|
||||
|
||||
function useDeepEqual (input) {
|
||||
|
||||
const [value, setValue] = useState(input);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!deepEqual(input, value)) {
|
||||
setValue(input)
|
||||
}
|
||||
|
||||
}, [input, value]);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export default useDeepEqual;
|
||||
148
public/react/src/common/reactQuill/useMountQuill.js
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* @Description: 创建 reactQuill实例
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-09 09:31:42
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-17 20:42:05
|
||||
*/
|
||||
import Quill from 'quill'; // 导入quill
|
||||
import { useState, useEffect, useMemo } from 'react';
|
||||
import flatten from './flatten.js';
|
||||
import useDeepEqualMemo from './useDeepEqualMemo';
|
||||
import Katex from 'katex';
|
||||
import ImageBlot from './ImageBlot';
|
||||
import { fetchUploadImage } from '../../services/ojService.js';
|
||||
import { getImageUrl } from 'educoder'
|
||||
window.katex = Katex;
|
||||
|
||||
Quill.register(ImageBlot);
|
||||
|
||||
function useMountQuill ({
|
||||
element,
|
||||
options: passedOptions,
|
||||
uploadImage,
|
||||
showUploadImage,
|
||||
imgAttrs = {} // 指定图片的宽高属性
|
||||
}) {
|
||||
|
||||
// 是否引入 katex
|
||||
const [katexLoaded, setKatexLoaded] = useState(Boolean(window.katex))
|
||||
const [quill, setQuill] = useState(null);
|
||||
|
||||
const options = useDeepEqualMemo(passedOptions);
|
||||
console.log('use mount quill: ', passedOptions);
|
||||
|
||||
// 判断options中是否包含公式
|
||||
const requireKatex = useMemo(() => {
|
||||
return flatten(options.modules.toolbar).includes('formula');
|
||||
}, [options]);
|
||||
|
||||
// 加载katex
|
||||
useEffect(() => {
|
||||
if (!requireKatex) return;
|
||||
if (katexLoaded) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
if (window.katex) {
|
||||
setKatexLoaded(true);
|
||||
clearInterval(interval);
|
||||
}
|
||||
});
|
||||
|
||||
return () => { // 定义回调清除定时器
|
||||
clearInterval(interval);
|
||||
}
|
||||
|
||||
}, [
|
||||
setKatexLoaded,
|
||||
katexLoaded,
|
||||
requireKatex
|
||||
]);
|
||||
|
||||
// 加载 quill
|
||||
useEffect(() => {
|
||||
if (!element) return;
|
||||
if (requireKatex && !katexLoaded) {
|
||||
element.innerHTML = `
|
||||
<div style="color: #ddd">
|
||||
Loading Katex...
|
||||
</div>
|
||||
`
|
||||
}
|
||||
// 清空内容
|
||||
element.innerHTML = '';
|
||||
console.log(element);
|
||||
// 创建 quill 节点
|
||||
const quillNode = document.createElement('div');
|
||||
element.appendChild(quillNode); // 将quill节点追回到 element 元素中
|
||||
|
||||
const quill = new Quill(element, options);
|
||||
setQuill(quill);
|
||||
// 加载上传图片功能
|
||||
if (typeof uploadImage === 'function') {
|
||||
quill.getModule('toolbar').addHandler('image', (e) => {
|
||||
// 创建type类型输入框加载本地图片
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'file');
|
||||
input.setAttribute('accept', 'image/*');
|
||||
input.click();
|
||||
|
||||
input.onchange = async (e) => {
|
||||
const file = input.files[0]; // 获取文件信息
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
// const reader = new FileReader();
|
||||
// reader.readAsDataURL(file);
|
||||
// console.log('文件信息===>>', reader);
|
||||
// reader.onload = function (e) {
|
||||
// debugger;
|
||||
// console.log('文件信息===>>', e.target.result);
|
||||
// const image = new Image();
|
||||
// image.src = e.target.result;
|
||||
|
||||
// image.onload = function () {
|
||||
// // file.width =
|
||||
// console.log(image.width, image.height);
|
||||
// }
|
||||
// }
|
||||
|
||||
const range = quill.getSelection(true);
|
||||
let fileUrl = ''; // 保存上传成功后图片的url
|
||||
// 上传文件
|
||||
const result = await fetchUploadImage(formData);
|
||||
// 获取上传图片的url
|
||||
if (result.data && result.data.id) {
|
||||
fileUrl = getImageUrl(`api/attachments/${result.data.id}`);
|
||||
}
|
||||
// 根据id获取文件路径
|
||||
const { width, height } = imgAttrs;
|
||||
// console.log('上传图片的url:', fileUrl);
|
||||
if (fileUrl) {
|
||||
quill.insertEmbed(range.index, 'image', {
|
||||
url: fileUrl,
|
||||
alt: '',
|
||||
onClick: showUploadImage,
|
||||
width,
|
||||
height
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return () => {
|
||||
element.innerHTML = '';
|
||||
}
|
||||
}, [
|
||||
element,
|
||||
options,
|
||||
requireKatex,
|
||||
katexLoaded,
|
||||
]);
|
||||
|
||||
return quill;
|
||||
}
|
||||
|
||||
export default useMountQuill;
|
||||
60
public/react/src/common/reactQuill/useQuill.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-09 09:09:50
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-17 15:46:50
|
||||
*/
|
||||
import useQuillPlaceholder from './useQuillPlaceholder';
|
||||
import useQuillValueSync from './useQuillValueSync';
|
||||
import useQuillOnChange from './useQuillOnChange';
|
||||
import useMountQuill from './useMountQuill';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
function useQuill ({
|
||||
disallowColors,
|
||||
placeholder,
|
||||
uploadImage,
|
||||
onChange,
|
||||
options,
|
||||
value,
|
||||
element,
|
||||
showUploadImage
|
||||
}) {
|
||||
|
||||
// 获取 quill 实例
|
||||
const quill = useMountQuill({
|
||||
element,
|
||||
options,
|
||||
uploadImage,
|
||||
showUploadImage
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (disallowColors && quill) {
|
||||
quill.clipboard.addMatcher(Node.ELEMENT_NODE, (node, delta) => {
|
||||
delta.ops = delta.ops.map(op => {
|
||||
if (op.attributes && op.attributes.color) {
|
||||
const { color, ...attributes } = op.attributes;
|
||||
return {
|
||||
...op,
|
||||
attributes
|
||||
}
|
||||
}
|
||||
return op;
|
||||
});
|
||||
return delta;
|
||||
});
|
||||
}
|
||||
}, [
|
||||
disallowColors,
|
||||
quill
|
||||
]);
|
||||
|
||||
useQuillPlaceholder(quill, placeholder);
|
||||
useQuillValueSync(quill, value);
|
||||
useQuillOnChange(quill, onChange);
|
||||
}
|
||||
|
||||
export default useQuill;
|
||||
33
public/react/src/common/reactQuill/useQuillOnChange.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-12 19:49:11
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-16 11:39:27
|
||||
*/
|
||||
import { useEffect } from 'react';
|
||||
|
||||
function useQuillOnChange (quill, onChange) {
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!quill) return;
|
||||
if (typeof onChange !== 'function') return;
|
||||
|
||||
let handler;
|
||||
|
||||
quill.on(
|
||||
'text-change',
|
||||
(handler = () => {
|
||||
onChange(quill.getContents()); // getContents: 检索编辑器内容
|
||||
})
|
||||
);
|
||||
|
||||
return () => {
|
||||
quill.off('text-change', handler);
|
||||
}
|
||||
}, [quill, onChange]);
|
||||
}
|
||||
|
||||
export default useQuillOnChange;
|
||||
22
public/react/src/common/reactQuill/useQuillPlaceholder.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-12-09 09:28:34
|
||||
* @LastEditors: tangjiang
|
||||
* @LastEditTime: 2019-12-16 11:39:48
|
||||
*/
|
||||
import { useEffect } from 'react'
|
||||
|
||||
function useQuillPlaceholder (
|
||||
quill,
|
||||
placeholder
|
||||
) {
|
||||
|
||||
useEffect(() => {
|
||||
if (!quill || !quill.root) return;
|
||||
quill.root.dataset.placeholder = placeholder;
|
||||
}, [quill, placeholder]);
|
||||
}
|
||||
|
||||
export default useQuillPlaceholder;
|
||||
31
public/react/src/common/reactQuill/useQuillValueSync.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import deepEqual from './deepEqual.js'
|
||||
|
||||
function useQuillValueSync(quill, value) {
|
||||
const [selection, setSelection] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!quill) return
|
||||
|
||||
const previous = quill.getContents()
|
||||
const current = value
|
||||
|
||||
if (!deepEqual(previous, current)) {
|
||||
setSelection(quill.getSelection())
|
||||
if (typeof value === 'string') {
|
||||
quill.clipboard.dangerouslyPasteHTML(value, 'api')
|
||||
} else {
|
||||
quill.setContents(value)
|
||||
}
|
||||
}
|
||||
}, [quill, value, setSelection])
|
||||
|
||||
useEffect(() => {
|
||||
if (quill && selection) {
|
||||
quill.setSelection(selection)
|
||||
setSelection(null)
|
||||
}
|
||||
}, [quill, selection, setSelection])
|
||||
}
|
||||
|
||||
export default useQuillValueSync
|
||||
135
public/react/src/common/util/ShareUtil.js
Normal file
@@ -0,0 +1,135 @@
|
||||
import axios from 'axios'
|
||||
const host = window.location.protocol + '//' + window.location.host
|
||||
const wx = window.wx
|
||||
function share(shareData) {
|
||||
try {
|
||||
wx.onMenuShareAppMessage(shareData);//分享给好友
|
||||
wx.onMenuShareTimeline(shareData);//分享到朋友圈
|
||||
wx.onMenuShareQQ(shareData);//分享给手机QQ
|
||||
wx.onMenuShareWeibo(shareData);//分享腾讯微博
|
||||
wx.onMenuShareQZone(shareData);//分享到QQ空间
|
||||
} catch(e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
const urlDoneMap = {}
|
||||
function requestForSignatrue (callback) {
|
||||
const currentUrl = window.location.href.split('#')[0]
|
||||
|
||||
if (window.wx) {
|
||||
if (urlDoneMap[currentUrl]) {
|
||||
callback && callback()
|
||||
} else {
|
||||
const wx = window.wx
|
||||
const url = '/wechats/js_sdk_signature.json'
|
||||
urlDoneMap[currentUrl] = true
|
||||
// window.encodeURIComponent()
|
||||
axios.post(url, {
|
||||
url: window.__testUrl || currentUrl,
|
||||
}).then((response) => {
|
||||
console.log('got res')
|
||||
const data = response.data;
|
||||
wx.config({
|
||||
debug: false,
|
||||
appId: data.appid,
|
||||
timestamp: data.timestamp,
|
||||
nonceStr: data.noncestr,
|
||||
signature: data.signature,
|
||||
jsApiList: [
|
||||
'onMenuShareTimeline',//
|
||||
'onMenuShareAppMessage',
|
||||
'onMenuShareQQ',
|
||||
'onMenuShareWeibo',
|
||||
'onMenuShareQZone'
|
||||
]
|
||||
});
|
||||
wx.ready(function () {
|
||||
callback && callback()
|
||||
});
|
||||
wx.error(function (res) {
|
||||
console.log('wx is error')
|
||||
console.log(res)
|
||||
//alert(res.errMsg);//错误提示
|
||||
});
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
实践课程 平台提供涵盖基础入门、案例实践和创新应用的完整实训项目体系,通过由浅入深的实训路径,帮助学生快速提升实战能力。
|
||||
实训项目 覆盖不同专业的IT实验和实训,每周更新,无需配置本机实验环境,随时随地开启企业级真实实训。
|
||||
翻转课堂 自动评测实训任务,支持技能统计,提供教学活动分析报告,减轻教师和助教的辅导压力,免去作业发布和批改的困扰,实时了解学生学习情况,全面提升教师施教效率和水平。
|
||||
单个课程和实训 获取课程/实训的简介 该课程或者实训展示的缩略图
|
||||
|
||||
*/
|
||||
export function configShareForIndex (path) {
|
||||
requestForSignatrue(() => {
|
||||
var shareData = {
|
||||
title: 'EduCoder - 首页',
|
||||
desc: 'Educoder是一个面向计算机类的互联网IT教育和实战平台,提供企业级工程实训,以实现工程化专业教学的自动化和智能化。高校和企业人员可以在此开展计算机实践性教学活动,将传统的知识传授和时兴的工程实战一体化。',
|
||||
link: host + (path || ''),
|
||||
imgUrl: window.__testImageUrl
|
||||
|| host + '/react/build/images/share_logo_icon.jpg'
|
||||
};
|
||||
share(shareData)
|
||||
})
|
||||
}
|
||||
export function configShareForPaths () {
|
||||
requestForSignatrue(() => {
|
||||
console.log('configShareForPaths', host)
|
||||
var shareData = {
|
||||
title: 'EduCoder - 实践课程',
|
||||
desc: '平台提供涵盖基础入门、案例实践和创新应用的完整实训项目体系,通过由浅入深的实训路径,帮助学生快速提升实战能力。',
|
||||
link: `${host}/paths`,
|
||||
imgUrl: window.__testImageUrl
|
||||
|| host + '/react/build/images/share_logo_icon.jpg'
|
||||
};
|
||||
share(shareData)
|
||||
})
|
||||
}
|
||||
export function configShareForShixuns () {
|
||||
requestForSignatrue(() => {
|
||||
console.log('configShareForShixuns', host)
|
||||
|
||||
var shareData = {
|
||||
title: 'EduCoder - 实训项目',
|
||||
desc: '覆盖不同专业的IT实验和实训,每周更新,无需配置本机实验环境,随时随地开启企业级真实实训。',
|
||||
link: `${host}/shixuns`,
|
||||
imgUrl: window.__testImageUrl
|
||||
|| host + '/react/build/images/share_logo_icon.jpg'
|
||||
};
|
||||
share(shareData)
|
||||
})
|
||||
}
|
||||
export function configShareForCourses () {
|
||||
requestForSignatrue(() => {
|
||||
console.log('configShareForCourses', host)
|
||||
|
||||
var shareData = {
|
||||
title: 'EduCoder - 翻转课堂',
|
||||
desc: '自动评测实训任务,支持技能统计,提供教学活动分析报告,减轻教师和助教的辅导压力,免去作业发布和批改的困扰,实时了解学生学习情况,全面提升教师施教效率和水平。',
|
||||
link: `${host}/courses`,
|
||||
imgUrl: window.__testImageUrl
|
||||
|| host + '/react/build/images/share_logo_icon.jpg'
|
||||
};
|
||||
share(shareData)
|
||||
})
|
||||
}
|
||||
|
||||
// detail
|
||||
export function configShareForCustom (title, desc, imgUrl, path) {
|
||||
requestForSignatrue(() => {
|
||||
console.log('configShareForCustom', host)
|
||||
const _url = window.location.href.split('#')[0];
|
||||
var shareData = {
|
||||
title: title,
|
||||
desc: desc,
|
||||
link: path ? `${host}/${path}` : _url,
|
||||
imgUrl: imgUrl || window.__testImageUrl
|
||||
|| host + '/react/build/images/share_logo_icon.jpg'
|
||||
};
|
||||
share(shareData)
|
||||
})
|
||||
}
|
||||
136
public/react/src/constants/index.js
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* @Description:
|
||||
* @Author: tangjiang
|
||||
* @Github:
|
||||
* @Date: 2019-11-20 23:10:48
|
||||
* @LastEditors : tangjiang
|
||||
* @LastEditTime : 2020-01-02 14:57:02
|
||||
*/
|
||||
const CONST = {
|
||||
jcLabel: {
|
||||
name: '任务名称',
|
||||
language: '编程语言',
|
||||
description: '描述',
|
||||
difficult: '难易度',
|
||||
category: '课程',
|
||||
openOrNot: '公开程序',
|
||||
timeLimit: '时间限制',
|
||||
knowledge: '知识点',
|
||||
sub_discipline_id: '课程'
|
||||
},
|
||||
fontSetting: {
|
||||
title: '代码格式',
|
||||
type: 'select',
|
||||
content: [
|
||||
{
|
||||
text: '显示风格',
|
||||
type: 'style',
|
||||
value: [
|
||||
{
|
||||
key: 'dark',
|
||||
text: '黑色背景',
|
||||
value: 'dark'
|
||||
},
|
||||
{
|
||||
key: 'light',
|
||||
text: '白色背景',
|
||||
value: 'light'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
text: '字体大小',
|
||||
type: 'font',
|
||||
value: [
|
||||
{
|
||||
key: 1,
|
||||
text: '12px',
|
||||
value: 12
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
text: '14px',
|
||||
value: 14
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
text: '16px',
|
||||
value: 16
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
text: '18px',
|
||||
value: 18
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
text: '24px',
|
||||
value: 24
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
text: '30px',
|
||||
value: 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
opacitySetting: {
|
||||
title: '代码格式',
|
||||
type: 'label',
|
||||
content: [
|
||||
{
|
||||
text: '字体大小',
|
||||
value: 'CTRL + S'
|
||||
},
|
||||
{
|
||||
text: '唤出快捷键列表',
|
||||
value: 'F1/ALT + F1'
|
||||
},
|
||||
{
|
||||
text: '向左缩进',
|
||||
value: 'CTRL + ['
|
||||
},
|
||||
{
|
||||
text: '向右缩进',
|
||||
value: 'CTRL + ]'
|
||||
},
|
||||
{
|
||||
text: '跳到匹配的括号',
|
||||
value: 'CTRL + SHIFT + \\'
|
||||
},
|
||||
{
|
||||
text: '转到行首',
|
||||
value: 'HOME'
|
||||
},
|
||||
{
|
||||
text: '转到行尾',
|
||||
value: 'END'
|
||||
}
|
||||
]
|
||||
},
|
||||
tagBackground: {
|
||||
1: '#52c41a',
|
||||
2: '#faad14',
|
||||
3: '#f5222d'
|
||||
},
|
||||
diffText: {
|
||||
1: '简单',
|
||||
2: '中等',
|
||||
3: '困难'
|
||||
},
|
||||
reviewResult: {
|
||||
'-1': '测试用例结果不匹配',
|
||||
'0': '评测通过',
|
||||
'1': '',
|
||||
'2': '评测超时',
|
||||
'3': '评测pod失败',
|
||||
'4': '编译失败',
|
||||
'5': '执行失败'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default CONST;
|
||||
|
||||
50
public/react/src/context/EffectDisplayContent.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
class EffectDisplayContent extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { typeName, content1, content2, content3 } = this.props;
|
||||
return (
|
||||
<div className="task-popup-content effectDisplay">
|
||||
<style>{`
|
||||
.effectDisplay .content_title {
|
||||
flex: 1 1 0
|
||||
}
|
||||
.effectDisplay .content>div {
|
||||
flex: 1
|
||||
}
|
||||
.effectDisplay .clappr, .effectDisplay .contentWrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.effectDisplay .clappr>div {
|
||||
width: 400px !important;
|
||||
}
|
||||
`}</style>
|
||||
<div className="clearfix df">
|
||||
{content1 && <p className="content_title edu-txt-center fl mr03precent font-18">原始{typeName}</p>}
|
||||
{content2 && <p className="content_title edu-txt-center fl font-18 mr03precent">实际输出{typeName}</p>}
|
||||
{content3 && <p className="content_title edu-txt-center fl font-18 mr03precent">预期输出{typeName}</p>}
|
||||
</div>
|
||||
<div className="clearfix df content" >
|
||||
{content1 && <div className="fl mr03precent pt10 mb50 contentWrap">
|
||||
{content1}
|
||||
</div>}
|
||||
{content2 && <div className="fl mr03precent pt10 mb50 contentWrap">
|
||||
{content2}
|
||||
</div>}
|
||||
{content3 && <div className="fl mr03precent pt10 mb50 contentWrap">
|
||||
{content3}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EffectDisplayContent;
|
||||
251
public/react/src/context/EvaluateSuccessEffectDisplay.js
Normal file
@@ -0,0 +1,251 @@
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { Redirect } from 'react-router';
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import { Clappr } from 'educoder'
|
||||
import axios from 'axios';
|
||||
import EffectDisplayContent from './EffectDisplayContent'
|
||||
class EvaluateSuccessEffectDisplay extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps, newContext) {
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.type == 'html') {
|
||||
const iframe = document.getElementById('_displayIframe')
|
||||
if (iframe && iframe.contentWindow && this.props.iframe_src) {
|
||||
iframe.contentWindow.open()
|
||||
iframe.contentWindow.document.write(this.props.iframe_src);
|
||||
iframe.contentWindow.document.close();
|
||||
} else {
|
||||
console.error('not mounted')
|
||||
}
|
||||
}
|
||||
}
|
||||
hidepicture = () => {
|
||||
const dom = document.getElementById('picture_display');
|
||||
ReactDOM.unmountComponentAtNode(dom)
|
||||
// window.$('#picture_display').hide();
|
||||
|
||||
window.$('.data-tip-right').hide()
|
||||
}
|
||||
renderContent = () => {
|
||||
// qrcode
|
||||
// const type = 'image' // 'qrcode'
|
||||
const { type, qrcode_str,
|
||||
answer_picture, orignal_picture, user_picture, contents,
|
||||
user_file, answer_file, orignal_file } = this.props;
|
||||
if (type == 'qrcode') {
|
||||
// 单张图片,比如安卓评测完显示qrcode
|
||||
return (
|
||||
<div style={{ textAlign: 'center', paddingTop: '5%' }}>
|
||||
<p style={{ color: '#333', fontSize: '24px' }}>请使用Android手机浏览器扫码查看效果(暂不支持微信、QQ与支付宝扫一扫)</p>
|
||||
<p>
|
||||
{/* <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQAQAAAACoxAthAAAGiElEQVR4nO1c UY6jSgy0Q6TOH7lBc5POxSbAzMXgJnCD8EdLQ/pVmSGz+/52pcdDo46iEWGw 1GnZ5XLZnVP649dJ/viVTbJJNskm2eRYJovyJdJr1V7k9X6XsRF5j7g56lXs oduuCzuuSYkM2Ml0S30783M9VfWM7fL471vZp1k+RQo89Nh5Ycc16eFDi08X /4yVRG1cf4rVXSRd5GOWVqszHpr0vPvCDm8ynmQ0xuWXi29kbCflHgp97H9d 2CFNiodM4ekG+FXSCiCmyo9tOYhL/+fCDmkSVoQynP+YKgWgpQRMa+FpMQDH 3INo97n7wo5qMjELLtee1whGkRgaC0bF7s3cRqQFZtTrzgs7qklh5WIx3txw t5tvl7FxA2CtLnnn7SLRd/ZQt+vCjmyi1/5cJtW67J/4NAPKtOEFcwHC07Jk n3OlvKKyeASAlEsNGQWJqzgA/gDvwvsd1xNDMuPYl8lylXjtgVNRi6kClDEY Y/V28cUUTi4tF/ksuzTWS+b8r1xZjDXcLA13hxRJto9c2YgAxOoZ+A/uGhbf /cyoLB6AG4BSJ2K4lJqotVYfQCTXWwb0YF5u1AzjXyZwBdQ7uAALBS6BX6UB 2/U0hwEdBTWNpBKMxV0XdlgTkIRiVIBSSk8ZiwT09g2ZvFetQBhA45H14GZF Lqs3H0vRJ4ASInEKjVC0QZEIhgDOUFOa8EXqPiX8TFD6CxPgGHwoMuTurkea q5HvYtUavyKlNzKP7Yo5Kl9UAYkPF6bYSERNTSL6XPk8Prr+PN3AFhC+uy7s sCaAJ/VtAZLgCVmu514xBYST1YyAtSzd/GayaA2uyZrZqLvhWG3qTbMqhJMi nVKB9t2uCzuuSdnijcCkUE/ZIWllUZmWsic7dQPSAulqjsqtSASIBRY+1Z1h iCzptUxrQd1e+AT8C/83knvw77KTCTk/CIT1ODQxXZKGxVBffCI9W9WdNheJ soldHcoiUgcP+OJdx2bQu6XORJKGm/1yDUWukl4mZ3MzqjS9RaW0Gp5CBzOJ 1cdrQF0Zs49tufLG/uMjWN2N5Njq2nxkFnhHuVT2lKBRFmQfe0nQn5T/+lhp MtbqksLfrAvZRDJ/lgXTbe+FHdbEsiA27UblGailrCgB9BaS+FvP3npHfR4J +NWkR2C6QakK+qewOEqEMrF0YNRia7gd/7v89yYoGiUIO2jkFQ6+hHhkudSg RFLKZe5BxT5rF98m50nx5gxAdQeazfSrdmILEvTsyVxpcxZZu3ipPayCUhJQ /eHdpafrWxLXUUtQsoAsALYPHJPM+V+KIurKyHkwnYxd2CRAY13Iu4xpHqkG lV1upcmmWuuKYyKpHDjaFJXy9YwU4E9C7dq0i1tWrb9NzpYrt+428qOa1N9e RkToc20FpO5slOzo32UHE/aStEYVNHMArJFV25e79d2Ugr9fqIyFvRd2XJPp RmHCd5exmMJSoiwCax1PBmu2bwzc5dpnHHuZhPNUg/NPoUZylNXT/MIJCjZ5 TzJG62cuWYPdNNgOudI9AtOiR2YkE3O91UqD2hSiTJqsebLvwg5rAu5KGbYn ask7yiL7CzJWK7ZuaLXCplIQysj/8rHPskVsln27AdfaJdH5KxGcrV7POCYb 5w/nr0T4VoJRyAcrI5aZtQ2ft8gFZGIhc355qT0LCH/ZzTbRytYbO5Vp5hAd NhBedzZt2+Ve0jeOUR/TtW1krKxWutaHnQQp0hCvYlXUzgs7rkngCBlCbmw5 BDWuzL8GpvGAAx2P3kW063Ze2EFNWC5OdVRSfRP2xQ1vJGayXss2mvEz9bHF t58sEtsLu7QNdsAmT+5GSp82RIGn1Le5MbSZTDf1nLSnUMPXnSM6FL7uUcHe i6lKKcGnfqbD/IWJndDjpH3UYu1xcxiMzY6WmxaePD3Ek5FZtP/VJCDkrCps KHnxxFBNkgB+NSrJFXscuUj8Mnmdrk0EMSqEQjy340Lj1kojh8/y4GZip2sR m3QwslBUiA372oPYAdv1lRtDv5pY10dvxhOQKOtJa060+vWMFctqPhLyUOu/ TBCVyWZZsW8mRH9N04kjV0XlnaPyNxMUgRw4QXnIwQDBphmBb+wIPNKCyyeG fjFZm/1Il23qkSLB5NOFvw/QzgQ0ccPZBhIzjm0m0/obE/3MOfy7oRmPVs2+ tlyAN2Afm5pHm+SbwYLTS7AJ85YHZ7xtFDfNJk9sr3K7Nptkk2ySTX6EyT81 +NIc7MmVogAAAABJRU5ErkJggg== ">
|
||||
</img> */}
|
||||
<img src={`data:image/png;base64,${qrcode_str}`}>
|
||||
</img>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (type == 'image') {
|
||||
return (
|
||||
<div className="task-popup-content">
|
||||
<div className="clearfix">
|
||||
{orignal_picture[0] && <p className="edu-txt-center fl with33 mr03precent font-18">原始图片</p>}
|
||||
<p className="edu-txt-center fl font-18 with33 mr03precent">实际输出图片</p>
|
||||
<p className="edu-txt-center fl font-18 with33 mr03precent">预期输出图片</p>
|
||||
</div>
|
||||
<div className="clearfix" id="picture-content">
|
||||
{orignal_picture[0] && <div className="fl with33 mr03precent pt10 mb50">
|
||||
{orignal_picture.map(item => {
|
||||
return (
|
||||
<img alt="Icon"
|
||||
src={ item.pic_url}/> )
|
||||
})}
|
||||
{/* {orignal_picture[0] && <img alt="Icon"
|
||||
src={ orignal_picture[0].pic_url}/>} */}
|
||||
</div>}
|
||||
<div className="fl with33 mr03precent pt10 mb50">
|
||||
{user_picture.map(item => {
|
||||
return (
|
||||
<img alt="Icon"
|
||||
src={ item.pic_url}/> )
|
||||
})}
|
||||
</div>
|
||||
<div className="fl with33 mr03precent pt10 mb50">
|
||||
{answer_picture.map(item => {
|
||||
return (
|
||||
<img alt="Icon"
|
||||
src={ item.pic_url}/> )
|
||||
})}
|
||||
{/* { answer_picture[0] && <img alt="Icon"
|
||||
src={ answer_picture[0].pic_url}/> } */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (type == "txt") {
|
||||
return (
|
||||
<div className="task-popup-content clearfix">
|
||||
<div className="with80" style={{margin: '0 auto'}}>
|
||||
<p className="color-blue font-18 mb20 edu-txt-center">实际输出</p>
|
||||
<textarea className="output-txt" readonly="" defaultValue={contents}></textarea>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else if (type == "html") {
|
||||
return (
|
||||
<iframe id="_displayIframe"></iframe>
|
||||
)
|
||||
} else if (type == 'mp3') {
|
||||
return (
|
||||
<EffectDisplayContent
|
||||
typeName="音频"
|
||||
content1={ orignal_file[0] && orignal_file[0].file_url
|
||||
? <Clappr source={orignal_file[0].file_url} id="1" className="clappr" type="mp3"></Clappr> : null }
|
||||
content2={ user_file[0] && user_file[0].file_url
|
||||
? <Clappr source={user_file[0].file_url} id="2" className="clappr" type="mp3"></Clappr> : null }
|
||||
content3={ answer_file[0] && answer_file[0].file_url
|
||||
? <Clappr source={answer_file[0].file_url} id="3" className="clappr" type="mp3"></Clappr> : null }
|
||||
></EffectDisplayContent>
|
||||
)
|
||||
} else if (type == 'mp4') {
|
||||
return (
|
||||
<EffectDisplayContent
|
||||
typeName="视频"
|
||||
content1={ orignal_file[0] && orignal_file[0].file_url
|
||||
? <Clappr source={orignal_file[0].file_url} id="1" className="clappr" type="mp4"></Clappr> : null }
|
||||
content2={ user_file[0] && user_file[0].file_url
|
||||
? <Clappr source={user_file[0].file_url} id="2" className="clappr" type="mp4"></Clappr> : null }
|
||||
content3={ answer_file[0] && answer_file[0].file_url
|
||||
? <Clappr source={answer_file[0].file_url} id="3" className="clappr" type="mp4"></Clappr> : null }
|
||||
></EffectDisplayContent>
|
||||
)
|
||||
}
|
||||
|
||||
/* <div className="with49 fr">
|
||||
<p className="font-18 mb20 edu-txt-center">预期输出</p>
|
||||
<textarea className="output-txt"></textarea>
|
||||
</div> */
|
||||
|
||||
}
|
||||
render() {
|
||||
const { tpmLoading } = this.props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<style>
|
||||
{`
|
||||
.task-popup-content {
|
||||
overflow-y: auto;
|
||||
padding-bottom: 55px;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
iframe#_displayIframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div className="photo_display">
|
||||
<div className="task-popup">
|
||||
<div className="task-popup-title clearfix">
|
||||
<h3 className="fl color-grey3 mt4">查看效果</h3>
|
||||
<a href="javascript:void(0);" onClick={this.hidepicture}
|
||||
data-tip-left="关闭查看效果" className="pop_close fr">
|
||||
<i className="fa fa-times-circle font-18 link-color-grey mt5"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{ this.renderContent() }
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EvaluateSuccessEffectDisplay;
|
||||
|
||||
// const qrcode_str = "iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQAQAAAACoxAthAAAGjElEQVR4nO1c\nW66qShCtEhP4wxnATHBi2rKdmMwEZmD/QQKn7lqFuve9f+d+cMhJG7M3Kitp\nO/VYtarag/324yC//UiQBEmQBEmQfUEW5cNffk31l4gVvDB/+zYp7tGT+Kvz\npgvbL6REBnxIPBfDwqfIVEvef+V2kyoUVdB6Fslw03Pjhe0X0sGGhBv1xX9D\nOw63qQ5RDRd5r+NwxNtRj5svbO8QbI7191Gwb5eyb/EsBNsVSpv/7ML2CVlO\n3dTAtBDELoxjSjMz06L69WcXtktIgwiVPZu8u+VmsUH4as1uCGVTfZ00i3X+\nZLSbN1/YXiGRWXCp2kgbg0vCMRHTlFuH6+rGW87MqKeNF7ZXSOblIiytQMjq\nlrK/CgO+4WUh7qQD7vKbHpsubMeQ7ohnPJshZDkfA6NQOGZrfUAiyOGP/Hz7\nhe0W0iwaMnvAE+GPMmTjAHsCDbuSmHU3Z7BTRcq28cL2CuFGdIhmcENtbA1c\nE2wMJLZTEIxYrwx3SXHszfkfc8k4NlaZgYMNTicq1EeLNrwGT9MgZYsbt13Y\nJhDQKhjMXLYesS3WuLCywxe/G9i7XBnpW1CJrRe2W0j2lGMMCNOje1Px+ojB\nvBgOU7MWiah38lQkvm0MXtZOlZWgB6Kv9Ed3QxiHsR3ybjkh0msKSh9IY0NA\nGI/gopQd7vREKhK8mJo21tmgSIzZcN54YTuFYCO0anGBIDa+yFU7yj1qKKpD\n3l+KSk/N/BOyzcJ2C0GxY88GNiTYn+5X3gfz8hAFoxsbOANSHvhXKhLlE/lJ\nrkAV6INZ1ANKnlVWRbqMTfCS51i2Cka68++yDQTWg+cxat6vb14UAd9ufm0e\nzRDHYIaW4tg3hLny4bwCe9WSw6PwoeolvmmzNNjR419JR/8fJAc9jecJoX74\nNdVfYOyUcdjgCCP4fEWGi1IysYsPZFLN7ZH395FZEi+XwjMmYn7eo9A+Cgn/\nnMrqt9j1WG1sFApcZS+sjzps13Vtrk20rgVhP3nlBwKCMbuIOlZIlEtpAQX1\n5M1HqcA35vKRPbtUJb0hdLYGNkSyOoDhw67U+pXHCismsgvqz8krP5AZiTAG\nMtjeXw9BlWwflWbZhwiXVUmC6gcCl1y7PqrgFciMZj3q8XYcDnBJwV85eu9o\n64XtFpIN4QjOXz7MGLsKaZUdInwS3EPhqqvasyTO/7axx0z2gEJp7WgzfOU9\nEkFbduYEAzF/TuziG4KN4IREo/XVGb7kHeJYKIRdIVd7lhPnLBLnl3cl3mVm\n2LSRlZG4vHOjgmGHaRXtDUXlXKZBnQ+kNI9jqCiXEvVRLZwEoFgt9NMBNSas\ni3Vl8sofXhmc898jjIoGpuD83hMJbBVViGCoDJI+9oaULeIUXG4Cw5fWqySh\nwk8yZkqT01M3nSQNg31D2Hws29z5qve4A/WxIYv13SiUZd6unVPkf7ELWk/H\nidXKEMQ4cMgC/B7ri9sbKibnY6lf+RMSQ/YEu3CXrA5THV6UTJErLwU2Fbny\nkbzyG5JZCwZLnV9Va9AJMH9GMPipahY1H85Mp9svbKcQz5VLZevAoTd2heqr\nrB1Mqj1gts+kXXxDGm/AiTbZOnQBtm+dS4vcLqNXdkdsalKt35yfE62I/Ngl\n8FUfamq9M/6V9/eot6nJ7TGdklf+gMCGONtDCfGa98L8aItPUF+Fs2FegzdJ\ntf6GzNJkLwZLyYLjrDzRoBwqINk4vu7afGH7hCzVg4kwno2i61duriu+RqYv\n3hnnjtojTx3ej3ax5ko6IHu7NDCploLqK+grOC1lWmGhtOnCtoEgn9mz4yYM\nPj5RmU/pZGbYBy0qlD/5kwQ+0dE3JJ61skWDVKv4IJSdq0BxlQ3uNuqkHBZL\nk/YviJ/QWzuLLjuw4UhjQ71DXgob6/Nnx47tX+li/xfSTNVD1ZxQSd6tJ4ZY\n+/ib02k9VPQHFrZHyOt0bTyPNCevdyryBCfzYUSVTQkiCRE/IH66FpU1eIJQ\nf6DszEoHKYC5z3x8bn6fKN33d9kI4nsRg2v1nJqgYq8+sdMEFwwRx9JIwH8h\nVKG7bOR44d0b3EvZqdN4yXvEOaqpqeT5F6REoryQiHJEc+XtV44H8NAQsiQq\n79RK+0A8EQ7KGTCfZUWuHDl56OM6glywqqmplfaG+Ola2Bh/RYE0DKGMh9FG\nvmwj9s2Okb9Ukdoc8s1gebpWeDK0+sWymqTixl9UwF9aHT5PxxkSJEESJEH+\nDsg/oT4AK5IwWw0AAAAASUVORK5CYII=\n"
|
||||
|
||||
const content = `
|
||||
AUC值为:0.9786487367132528
|
||||
你的得分为:782.9189893706023
|
||||
你的处理结果如下:
|
||||
id type
|
||||
155 9 0
|
||||
273 13 1
|
||||
1236 15 1
|
||||
618 18 1
|
||||
851 20 1
|
||||
64 24 1
|
||||
366 27 0
|
||||
42 30 1
|
||||
1468 31 1
|
||||
1059 32 0
|
||||
1311 33 1
|
||||
217 34 1
|
||||
486 44 1
|
||||
796 46 1
|
||||
505 48 1
|
||||
661 52 1
|
||||
1455 53 0
|
||||
514 62 0
|
||||
43 66 1
|
||||
930 69 0
|
||||
393 70 0
|
||||
574 71 1
|
||||
1175 74 1
|
||||
99 80 1
|
||||
441 81 1
|
||||
71 85 1
|
||||
971 89 1
|
||||
361 91 1
|
||||
449 94 1
|
||||
517 96 0
|
||||
... ... ...
|
||||
1031 4901 1
|
||||
1065 4902 0
|
||||
153 4903 1
|
||||
1141 4910 1
|
||||
1028 4916 1
|
||||
1021 4917 1
|
||||
272 4918 1
|
||||
809 4919 1
|
||||
557 4921 0
|
||||
1017 4922 1
|
||||
313 4924 1
|
||||
529 4926 1
|
||||
1350 4929 1
|
||||
891 4933 1
|
||||
989 4940 1
|
||||
1213 4941 1
|
||||
821 4943 1
|
||||
939 4955 1
|
||||
1470 4962 1
|
||||
1158 4963 1
|
||||
38 4967 1
|
||||
609 4969 1
|
||||
425 4976 1
|
||||
836 4982 1
|
||||
1387 4989 1
|
||||
927 4991 0
|
||||
417 4996 1
|
||||
428 4997 1
|
||||
752 4999 1
|
||||
1148 5000 1
|
||||
|
||||
[1500 rows x 2 columns]`
|
||||
4
public/react/src/context/TPIContext.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import React from 'react';
|
||||
const TPIContext = React.createContext('tpi')
|
||||
|
||||
export default TPIContext;
|
||||
969
public/react/src/context/TPIContextProvider.js
Normal file
@@ -0,0 +1,969 @@
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import axios from 'axios';
|
||||
import Snackbar from 'material-ui/Snackbar';
|
||||
import Fade from 'material-ui/transitions/Fade';
|
||||
|
||||
import update from 'immutability-helper'
|
||||
|
||||
import Dialog, {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
} from 'material-ui/Dialog';
|
||||
|
||||
import Button from 'material-ui/Button';
|
||||
|
||||
import EvaluateSuccessEffectDisplay from './EvaluateSuccessEffectDisplay'
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
/*
|
||||
若干js库
|
||||
http://inorganik.github.io/countUp.js/
|
||||
|
||||
*/
|
||||
/*
|
||||
切下一关需要更新:
|
||||
LeftViewContainer state.gameAnswer
|
||||
*/
|
||||
|
||||
import TPIContext from './TPIContext'
|
||||
import { EDU_ADMIN, EDU_SHIXUN_MANAGER, EDU_SHIXUN_MEMBER, EDU_CERTIFICATION_TEACHER
|
||||
, EDU_GAME_MANAGER, EDU_TEACHER, EDU_NORMAL, EDU_BUSINESS, CNotificationHOC ,getRandomNumber} from 'educoder'
|
||||
import { MuiThemeProvider, createMuiTheme, withStyles } from 'material-ui/styles';
|
||||
import MUIDialogStyleUtil from '../modules/page/component/MUIDialogStyleUtil'
|
||||
|
||||
const styles = MUIDialogStyleUtil.getTwoButtonStyle()
|
||||
|
||||
// 主题自定义
|
||||
const theme = createMuiTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#4CACFF',
|
||||
contrastText: 'rgba(255, 255, 255, 0.87)'
|
||||
},
|
||||
secondary: { main: '#4CACFF' }, // This is just green.A700 as hex.
|
||||
},
|
||||
});
|
||||
|
||||
const testSetsExpandedArrayInitVal = [false, false, false, false, false,
|
||||
false, false, false, false, false,
|
||||
false, false, false, false, false,
|
||||
false, false, false, false, false]
|
||||
window.__fetchAllFlag = false; // 是否调用过fetchAll TODO 如何多次使用provider?
|
||||
|
||||
const $ = window.$
|
||||
class TPIContextProvider extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.onRunCodeTestFinish = this.onRunCodeTestFinish.bind(this)
|
||||
this.onRunChooseTestFinish = this.onRunChooseTestFinish.bind(this)
|
||||
this.testSetUnlock = this.testSetUnlock.bind(this)
|
||||
|
||||
this.onTestSetHeaderClick = this.onTestSetHeaderClick.bind(this)
|
||||
|
||||
this.onShowPrevStage = this.onShowPrevStage.bind(this)
|
||||
this.onShowNextStage = this.onShowNextStage.bind(this)
|
||||
|
||||
this.readGameAnswer = this.readGameAnswer.bind(this)
|
||||
this.praisePlus = this.praisePlus.bind(this)
|
||||
|
||||
this.onGamePassed = this.onGamePassed.bind(this)
|
||||
|
||||
this.onPathChange = this.onPathChange.bind(this)
|
||||
|
||||
this.showSnackbar = this.showSnackbar.bind(this)
|
||||
this.showDialog = this.showDialog.bind(this)
|
||||
|
||||
this.onShowUpdateDialog = this.onShowUpdateDialog.bind(this)
|
||||
this.updateDialogClose = this.updateDialogClose.bind(this)
|
||||
|
||||
// this.showEffectDisplay();
|
||||
|
||||
this.state = {
|
||||
loading: true, // 正在加载数据
|
||||
gDialogOpen: false,
|
||||
currentGamePassed: false, // 当前game评测通过
|
||||
currentPassedGameGainGold: 0, // 当前通过的game获得的金币数
|
||||
currentPassedGameGainExperience: 0, // 当前通过的game获得的经验数
|
||||
|
||||
user: {},
|
||||
challenge: {},
|
||||
shixun_name: '',
|
||||
hide_code: false,
|
||||
|
||||
showUpdateDialog: false,
|
||||
|
||||
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0),
|
||||
}
|
||||
}
|
||||
|
||||
showEffectDisplay = (data) => {
|
||||
const dom = document.getElementById('picture_display');
|
||||
window.$(dom).show();
|
||||
ReactDOM.unmountComponentAtNode(dom)
|
||||
ReactDOM.render(<EvaluateSuccessEffectDisplay type={"qrcode"} {...data} />, dom);
|
||||
}
|
||||
|
||||
onShowUpdateDialog() {
|
||||
this.setState({showUpdateDialog: true})
|
||||
}
|
||||
// updateNowSuccess true 立即更新成功
|
||||
// TODO updateDialogClose方法名不对, 改为updateDialogCallback
|
||||
updateDialogClose(nextUpdateSuccess, updateNowSuccess) {
|
||||
const { myshixun } = this.state;
|
||||
if (nextUpdateSuccess) {
|
||||
myshixun.system_tip = true;
|
||||
}
|
||||
let { tpm_cases_modified, tpm_modified, tpm_script_modified } = this.state;
|
||||
if (updateNowSuccess) {
|
||||
tpm_cases_modified = false;
|
||||
tpm_modified = false;
|
||||
tpm_script_modified = false;
|
||||
}
|
||||
this.setState({
|
||||
myshixun,
|
||||
tpm_cases_modified,
|
||||
tpm_modified,
|
||||
tpm_script_modified,
|
||||
showUpdateDialog: false,
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.costTimeInterval && window.clearInterval(this.costTimeInterval)
|
||||
}
|
||||
componentDidMount() {
|
||||
|
||||
// TODO 登录状态的判断?
|
||||
// request
|
||||
// var shixunId = this.props.match.params.shixunId;
|
||||
var stageId = this.props.match.params.stageId;
|
||||
|
||||
window.__fetchAllFlag = false;
|
||||
this.fetchAll(stageId);
|
||||
this.costTimeInterval = window.setInterval(()=> {
|
||||
const { game } = this.state;
|
||||
if (!game || game.status === 2) { // 已完成的任务不需要计时
|
||||
return;
|
||||
}
|
||||
if (game.cost_time || game.cost_time === 0) {
|
||||
// game.cost_time += 1;
|
||||
this.setState({
|
||||
game: update(game, {cost_time: { $set: (game.cost_time+1) }})
|
||||
})
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
// 页面离开时存下用户的任务耗时
|
||||
|
||||
window.$(window).unload( ()=>{
|
||||
this._updateCostTime();
|
||||
});
|
||||
}
|
||||
// force 评测通过后,异步执行该方法,强制同步costTime到服务端
|
||||
_updateCostTime(async = false, force) {
|
||||
const { game, loading } = this.state;
|
||||
// TODO 还有一种情况,通关后cost_time计时停止,没法通过这个判断
|
||||
if (!force && (loading || !game || game.status === 2)) {
|
||||
return; // 已完成的任务不需要处理
|
||||
}
|
||||
let testPath = ''
|
||||
if (window.location.port == 3007) {
|
||||
testPath = 'http://test-newweb.educoder.net'
|
||||
}
|
||||
// var url = `${testPath}/api/v1/games/${ game.identifier }/cost_time`
|
||||
var url = `${testPath}/api/tasks/${ game.identifier }/cost_time${getRandomNumber()}`
|
||||
window.$.ajax({
|
||||
type: 'get',
|
||||
url: url,
|
||||
async: async, //IMPORTANT, the call will be synchronous
|
||||
data: {
|
||||
time: game.cost_time
|
||||
}
|
||||
}).done((data) => {
|
||||
console.log('complete');
|
||||
});
|
||||
}
|
||||
|
||||
onGamePassed(passed) {
|
||||
const { game } = this.state
|
||||
// 随便给个分,以免重新评测时又出现评星组件(注意:目前game.star没有显示在界面上,如果有则不能这么做)
|
||||
// game.star = 6;
|
||||
this.setState({
|
||||
game: update(game, {star: { $set: 6 }}),
|
||||
currentGamePassed: !!passed
|
||||
})
|
||||
}
|
||||
onTestSetHeaderClick(index) {
|
||||
// let { testSetsExpandedArray } = this.state;
|
||||
let testSetsExpandedArray;
|
||||
// 一次只打开一个
|
||||
if (this.state.testSetsExpandedArray[index] === false) {
|
||||
testSetsExpandedArray = testSetsExpandedArrayInitVal.slice(0);
|
||||
} else {
|
||||
testSetsExpandedArray = this.state.testSetsExpandedArray.slice(0);
|
||||
}
|
||||
testSetsExpandedArray[index] = !testSetsExpandedArray[index];
|
||||
this.setState({
|
||||
testSetsExpandedArray,
|
||||
})
|
||||
}
|
||||
|
||||
onShowPrevStage() {
|
||||
|
||||
}
|
||||
onShowNextStage() {
|
||||
window.__fetchAllFlag = false;
|
||||
console.log('onShowNextStage.........')
|
||||
// this.fetchAll('vznhx7mctwfq')
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps, oldProps) {
|
||||
var newStageId = newProps.match.params.stageId;
|
||||
if (!this.props || newStageId !== this.props.match.params.stageId) {
|
||||
window.__fetchAllFlag = false;
|
||||
this.fetchAll(newStageId)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// praise_tread/praise_plus?obj_id=569&obj_type=Challenge&horizontal=true&game_praise=true
|
||||
/*
|
||||
TODO 旧的接口在未登录时的返回值
|
||||
//获取登录页面地址
|
||||
var signinPath = '/';
|
||||
var htmlvalue = '<div class="task-popup" style="width:480px;"><div class="task-popup-title clearfix"><h3 class="fl color-grey3">提示</h3></div>'+
|
||||
'<div class="task-popup-content"><p class="task-popup-text-center font-16 mt10 mb10">您还没有登录,请登录后再执行此操作,谢谢!</p></div><div class="task-popup-right-sure clearfix">'+
|
||||
'<a href="javascript:void(0);" onclick="hideModal();" class="task-btn">取消</a><a href="' + signinPath + '" class="task-btn task-btn-orange ml15">登录</a></div></div>';
|
||||
pop_box_new(htmlvalue, 480, 182);
|
||||
*/
|
||||
praisePlus() {
|
||||
const { challenge, game } = this.state;
|
||||
let praise = true;
|
||||
const url = `/tasks/${game.identifier}/plus_or_cancel_praise.json`
|
||||
// const url = `/praise_tread/praise_plus?obj_id=${challenge.id}&obj_type=Challenge&horizontal=${praise}&game_praise=true`
|
||||
axios.post(url)
|
||||
.then((response) => {
|
||||
|
||||
if (response.data) {
|
||||
const { praise_count, praise } = response.data;
|
||||
// challenge.praise_count = praise_tread_count;
|
||||
// challenge.user_praise = praise;
|
||||
this.setState({ challenge: update(challenge,
|
||||
{
|
||||
praise_count: { $set: praise_count },
|
||||
user_praise: { $set: praise },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
onPathChange(index, callback) {
|
||||
let { challenge } = this.state;
|
||||
// challenge = Object.assign({}, challenge)
|
||||
// challenge.pathIndex = index;
|
||||
this.setState({
|
||||
challenge: update(challenge, {pathIndex: { $set: index }}),
|
||||
}, () => {
|
||||
callback && callback()
|
||||
})
|
||||
// TODO load new path content
|
||||
}
|
||||
|
||||
updateChallengePath = (path) => {
|
||||
const challenge = this.state.challenge;
|
||||
if (challenge.path === path) {
|
||||
return;
|
||||
}
|
||||
const { myshixun } = this.state;
|
||||
// myshixun.system_tip = false;
|
||||
|
||||
|
||||
challenge.path = path;
|
||||
const newChallenge = this.handleChallengePath(challenge);
|
||||
this.setState({ challenge: newChallenge,
|
||||
myshixun: update(myshixun, {system_tip: { $set: false }}),
|
||||
})
|
||||
}
|
||||
|
||||
handleChallengePath(challenge) {
|
||||
if (challenge.path && typeof challenge.path === "string") { // 多path的处理
|
||||
let path = challenge.path.split(';');
|
||||
_.remove(path, (item)=> !item)
|
||||
if (path.length > 1) {
|
||||
challenge.path = path;
|
||||
challenge.multiPath = true;
|
||||
} else {
|
||||
challenge.path = challenge.path.replace(';', '').trim() // 多path 改为单path 出现了 aaa.java;的情况
|
||||
challenge.multiPath = false;
|
||||
}
|
||||
}
|
||||
challenge.pathIndex = 0;
|
||||
return challenge;
|
||||
}
|
||||
|
||||
newResData2OldResData(newResData) {
|
||||
newResData.latest_output = newResData.last_compile_output
|
||||
// newResData.power
|
||||
newResData.record = newResData.record_onsume_time
|
||||
|
||||
// 老版用的hide_code
|
||||
newResData.hide_code = newResData.shixun.hide_code;
|
||||
|
||||
newResData.image_url = newResData.user.image_url
|
||||
newResData.grade = newResData.user.grade
|
||||
newResData.user_url = newResData.user.user_url
|
||||
newResData.username = newResData.user.name
|
||||
|
||||
newResData.output_sets = {}
|
||||
// newResData.output_sets.had_test_count = newResData.test_sets_count
|
||||
newResData.output_sets.test_sets = newResData.test_sets // JSON.stringify()
|
||||
newResData.output_sets.test_sets_count = newResData.test_sets_count
|
||||
// newResData.output_sets.had_passed_testsests_error_count = newResData.sets_error_count
|
||||
newResData.output_sets.had_passed_testsests_error_count = newResData.test_sets_count
|
||||
- newResData.sets_error_count
|
||||
// allowed_hidden_testset
|
||||
// sets_error_count
|
||||
// test_sets_count
|
||||
// test_sets
|
||||
// had_passed_testsests_error_count
|
||||
// test_sets
|
||||
// test_sets
|
||||
|
||||
return newResData
|
||||
}
|
||||
// 将若干数据重新组织一下
|
||||
_handleResponseData(resData_arg) {
|
||||
const resData = this.newResData2OldResData(Object.assign({}, resData_arg))
|
||||
let challenge = resData.challenge;
|
||||
challenge.isHtml = false;
|
||||
challenge.isWeb = false;
|
||||
challenge.isAndroid = false;
|
||||
challenge.showLanguagePictrue = false;
|
||||
challenge.hasAnswer = resData.has_answer;
|
||||
|
||||
let output_sets = resData.output_sets;
|
||||
if (resData.st === 0) { // 代码题
|
||||
challenge = this.handleChallengePath(challenge)
|
||||
|
||||
const mirror_name = (resData.mirror_name && resData.mirror_name.join)
|
||||
? resData.mirror_name.join(';') : (resData.mirror_name || '');
|
||||
if (mirror_name.indexOf('Html') !== -1) {
|
||||
challenge.isHtml = true;
|
||||
challenge.showLanguagePictrue = true;
|
||||
} else if (mirror_name.indexOf('Web') !== -1 || mirror_name.indexOf('JFinal') !== -1) {
|
||||
challenge.isWeb = true;
|
||||
} else if (mirror_name.indexOf('Android') !== -1) {
|
||||
challenge.isAndroid = true;
|
||||
}
|
||||
|
||||
if (output_sets && output_sets.test_sets && typeof output_sets.test_sets == 'string') {
|
||||
const test_sets_array = JSON.parse("[" + output_sets.test_sets + "]");
|
||||
output_sets.test_sets_array = test_sets_array;
|
||||
} else {
|
||||
output_sets.test_sets_array = output_sets.test_sets
|
||||
}
|
||||
|
||||
} else { // 选择题
|
||||
// 选择题题干markdown初始化
|
||||
const $ = window.$
|
||||
window.setTimeout(()=>{
|
||||
var lens = $("#choiceRepositoryView textarea").length;
|
||||
|
||||
for(var i = 1; i <= lens; i++){
|
||||
window.editormd.markdownToHTML("choose_subject_" + i, {
|
||||
htmlDecode: "style,script,iframe", // you can filter tags decode
|
||||
taskList: true,
|
||||
tex: true, // 数学公式
|
||||
// flowChart: true, // 默认不解析
|
||||
// sequenceDiagram: true // 默认不解析
|
||||
});
|
||||
}
|
||||
}, 400)
|
||||
}
|
||||
challenge.user_praise = resData.user_praise;
|
||||
challenge.praise_count = resData.praise_count;
|
||||
challenge.showWebDisplayButton = false;
|
||||
resData.challenge = challenge;
|
||||
|
||||
// 将一些属性写到game上
|
||||
let game = resData.game;
|
||||
game.prev_game = resData.prev_game;
|
||||
game.next_game = resData.next_game;
|
||||
if (game.status == 2) {
|
||||
// 已通关
|
||||
game.isPassThrough = true
|
||||
}
|
||||
resData.game = game;
|
||||
|
||||
const { tpm_cases_modified, tpm_modified, tpm_script_modified, myshixun } = resData;
|
||||
if (myshixun.system_tip) {
|
||||
// system_tip为true的时候 不弹框提示用户更新
|
||||
resData.showUpdateDialog = false
|
||||
} else {
|
||||
let needUpdateScript = (tpm_modified || tpm_script_modified) && challenge.st === 0;
|
||||
resData.showUpdateDialog = needUpdateScript || tpm_cases_modified
|
||||
}
|
||||
|
||||
/**
|
||||
email: "721773699@qq.com"
|
||||
grade: 213996
|
||||
identity: 1
|
||||
image_url: "avatars/User/1"
|
||||
login: "innov"
|
||||
name: "Coder"
|
||||
user_url: "/users/innov"
|
||||
*/
|
||||
let user = resData.user;
|
||||
user.username = resData.user.name;
|
||||
user.user_url = `/users/${resData.user.login}`;
|
||||
// user.image_url = resData.image_url;
|
||||
user.is_teacher = resData.is_teacher;
|
||||
resData.user = user;
|
||||
this._handleUserAuthor(resData)
|
||||
// TODO 测试
|
||||
// resData.power = 0;
|
||||
|
||||
resData.shixun.vnc = !!resData.vnc_url
|
||||
resData.shixun.vnc_evaluate = resData.vnc_evaluate
|
||||
|
||||
this.setState({
|
||||
...resData,
|
||||
currentGamePassed: false,
|
||||
loading: false,
|
||||
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0),
|
||||
})
|
||||
|
||||
window.document.title = resData.shixun.name
|
||||
|
||||
window.__myshixun = resData.myshixun; // tpi_html_show需要用到
|
||||
}
|
||||
_handleUserAuthor(resData) {
|
||||
// tpi tpm权限控制
|
||||
// const EDU_ADMIN = 1 // 超级管理员
|
||||
// const EDU_SHIXUN_MANAGER = 2 // 实训管理员
|
||||
// const EDU_SHIXUN_MEMBER = 3 // 实训成员
|
||||
// const EDU_CERTIFICATION_TEACHER = 4 // 平台认证的老师
|
||||
// const EDU_GAME_MANAGER = 5 // TPI的创建者
|
||||
// const EDU_TEACHER = 6 // 平台老师,但是未认证
|
||||
// const EDU_NORMAL = 7 // 普通用户
|
||||
|
||||
|
||||
/**
|
||||
EDU_ADMIN = 1 # 超级管理员
|
||||
EDU_BUSINESS = 2 # 运营人员
|
||||
EDU_SHIXUN_MANAGER = 3 # 实训管理员
|
||||
EDU_SHIXUN_MEMBER = 4 # 实训成员
|
||||
EDU_CERTIFICATION_TEACHER = 5 # 平台认证的老师
|
||||
EDU_GAME_MANAGER = 6 # TPI的创建者
|
||||
EDU_TEACHER = 7 # 平台老师,但是未认证
|
||||
EDU_NORMAL = 8 # 普通用户
|
||||
*/
|
||||
|
||||
// myshixun_manager power is_teacher
|
||||
resData.power = 0
|
||||
resData.myshixun_manager = false
|
||||
// resData.is_teacher = false
|
||||
|
||||
if (resData.user.identity === EDU_ADMIN) {
|
||||
resData.power = 1
|
||||
resData.myshixun_manager = true
|
||||
} else if (resData.user.identity === EDU_BUSINESS) {
|
||||
resData.power = 1
|
||||
resData.myshixun_manager = true
|
||||
} else if (resData.user.identity === EDU_SHIXUN_MANAGER) {
|
||||
resData.power = 1
|
||||
resData.myshixun_manager = true
|
||||
} else if (resData.user.identity === EDU_SHIXUN_MEMBER) {
|
||||
resData.power = 1
|
||||
resData.myshixun_manager = true
|
||||
} else if (resData.user.identity === EDU_CERTIFICATION_TEACHER) {
|
||||
resData.power = 1
|
||||
// 已认证老师允许跳关
|
||||
resData.myshixun_manager = true
|
||||
// resData.is_teacher = true
|
||||
|
||||
} else if (resData.user.identity === EDU_TEACHER) {
|
||||
// resData.is_teacher = true
|
||||
} else if (resData.user.identity === EDU_NORMAL) {
|
||||
|
||||
}
|
||||
return resData
|
||||
}
|
||||
|
||||
fetchAll(stageId, noTimeout) {
|
||||
|
||||
if (window.__fetchAllFlag == true ) {
|
||||
console.log('TPIContextProvider call fetchAll repeatly!')
|
||||
return;
|
||||
}
|
||||
// 切换关卡的时候,同步costTime
|
||||
this._updateCostTime(true);
|
||||
|
||||
if (!stageId) {
|
||||
// stageId = 'zl6kx8f7vfpo';
|
||||
// http://localhost:3000/myshixuns/so5w6iap97/stages/zl6kx8f7vfpo
|
||||
}
|
||||
|
||||
// var url = `/api/v1/games/${stageId}`
|
||||
var url = `/tasks/${stageId}.json`
|
||||
// {"status":1,"message":"undefined method `authenticate!' for #<Grape::Endpoint:0xc8c91c0>"}
|
||||
window.__fetchAllFlag = true;
|
||||
|
||||
|
||||
this.setState({
|
||||
loading: true,
|
||||
currentGamePassed: false, // 切换game时重置passed字段
|
||||
})
|
||||
|
||||
// test
|
||||
// var data = {"st":0,"discusses_count":0,"game_count":3,"record_onsume_time":0.36,"prev_game":null,"next_game":"7p9xwo2hklqv","praise_count":0,"user_praise":false,"time_limit":20,"tomcat_url":"http://47.96.157.89","is_teacher":false,"myshixun_manager":true,"game":{"id":2192828,"myshixun_id":580911,"user_id":57844,"created_at":"2019-09-03T15:50:49.000+08:00","updated_at":"2019-09-03T15:51:05.000+08:00","status":2,"final_score":0,"challenge_id":10010,"open_time":"2019-09-03T15:50:49.000+08:00","identifier":"hknvz4oaw825","answer_open":0,"end_time":"2019-09-03T15:51:04.000+08:00","retry_status":0,"resubmit_identifier":null,"test_sets_view":false,"picture_path":null,"accuracy":1.0,"modify_time":"2019-09-03T15:23:33.000+08:00","star":0,"cost_time":14,"evaluate_count":1,"answer_deduction":0},"challenge":{"id":10010,"shixun_id":3516,"subject":"1.1 列表操作","position":1,"task_pass":"[TOC]\n\n---\n\n####任务描述\n\n\n数据集a包含1-10共10个整数,请以a为输入数据,编写python程序,实现如下功能:\n①\t用2种方法输出a中所有奇数\n②\t输出大于3,小于7的偶数\n③\t用2种方法输出[1,2,3,…10,11,…20]\n④\t输出a的最大值、最小值。\n⑤\t用2种方法输出[10,9,…2,1]\n⑥\t输出[1,2,3,1,2,3,1,2,3,1,2,3]\n\n\n####相关知识\n\n\n请自行学习相关知识\n\n\n---\n开始你的任务吧,祝你成功!","score":100,"path":"1-1-stu.py","st":0,"web_route":null,"modify_time":"2019-09-03T15:23:33.000+08:00","exec_time":20,"praises_count":0},"shixun":{"id":3516,"name":"作业1——Python程序设计","user_id":77620,"gpid":null,"visits":23,"created_at":"2019-09-03T14:18:17.000+08:00","updated_at":"2019-09-03T15:58:16.000+08:00","status":0,"language":null,"authentication":false,"identifier":"6lzjig58","trainee":1,"major_id":null,"webssh":2,"homepage_show":false,"hidden":false,"fork_from":null,"can_copy":true,"modify_time":"2019-09-03T14:18:17.000+08:00","reset_time":"2019-09-03T14:18:17.000+08:00","publish_time":null,"closer_id":null,"end_time":null,"git_url":null,"vnc":null,"myshixuns_count":3,"challenges_count":3,"use_scope":0,"mirror_script_id":20,"image_text":null,"code_hidden":false,"task_pass":true,"exec_time":20,"test_set_permission":true,"sigle_training":false,"hide_code":false,"multi_webssh":false,"excute_time":null,"repo_name":"p09218567/6lzjig58","averge_star":5.0,"opening_time":null,"users_count":1,"forbid_copy":false,"pod_life":0},"myshixun":{"id":580911,"shixun_id":3516,"is_public":true,"user_id":57844,"gpid":null,"created_at":"2019-09-03T15:50:49.000+08:00","updated_at":"2019-09-03T15:59:04.000+08:00","status":0,"identifier":"k36hm4rwav","commit_id":"f25e1713882156480fc45ce0af57eff395a5037f","modify_time":"2019-09-03T14:18:17.000+08:00","reset_time":"2019-09-03T14:18:17.000+08:00","system_tip":false,"git_url":null,"onclick_time":"2019-09-03T15:50:49.000+08:00","repo_name":"p53276410/k36hm4rwav20190903155049"},"user":{"user_id":57844,"login":"p53276410","name":"文振乾","grade":24624,"identity":1,"image_url":"avatars/User/57844","school":"EduCoder团队"},"tpm_modified":true,"tpm_cases_modified":false,"mirror_name":["Python3.6"],"has_answer":false,"test_sets":[{"is_public":true,"result":true,"input":"","output":"result of a:\n[1, 3, 5, 7, 9]\n[1, 3, 5, 7, 9]\nresult of b:\n[2, 4, 6, 8, 10]\nresult of c:\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\nresult of d:\nThe minimum is:1\nThe maxium is:10\nresult of e:\n[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nresult of f:\n[10, 9, 8, 10, 9, 8, 10, 9, 8, 10, 9, 8]\n","actual_output":"result of a:\r\n[1, 3, 5, 7, 9]\r\n[1, 3, 5, 7, 9]\r\nresult of b:\r\n[2, 4, 6, 8, 10]\r\nresult of c:\r\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\r\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]\r\nresult of d:\r\nThe minimum is:1\r\nThe maxium is:10\r\nresult of e:\r\n[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\r\n[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\r\nresult of f:\r\n[10, 9, 8, 10, 9, 8, 10, 9, 8, 10, 9, 8]\r\n","compile_success":1,"ts_time":0.05,"ts_mem":8.77}],"allowed_unlock":true,"last_compile_output":"compile successfully","test_sets_count":1,"sets_error_count":0}
|
||||
// data.test_sets[0].actual_output = data.test_sets[0].actual_output.replace(/\r\n/g, '\n')
|
||||
// data.test_sets[0].output = data.test_sets[0].output.replace(/\r\n/g, '\n')
|
||||
// console.log(JSON.stringify(data))
|
||||
// data.shixun.vnc = true
|
||||
// data.vnc_url= "http://47.96.157.89:41158/vnc_lite.html?password=headless"
|
||||
|
||||
// this._handleResponseData(data)
|
||||
// return
|
||||
|
||||
axios.get(url, {
|
||||
// https://stackoverflow.com/questions/48861290/the-value-of-the-access-control-allow-origin-header-in-the-response-must-not-b
|
||||
// withCredentials: true,
|
||||
})
|
||||
.then((response) => {
|
||||
// {"status":1,"message":"Unauthorized. \u7528\u6237\u8ba4\u8bc1\u5931\u8d25."}
|
||||
|
||||
window.__fetchAllFlag = false;
|
||||
|
||||
if (response.data.status == 403) {
|
||||
window.location.href = "/403";
|
||||
return;
|
||||
}
|
||||
if (response.data.status == 404) {
|
||||
// 如果第一次发生404,则隔1s后再调用一次本接口;(因为ucloud主从同步可能有延迟)
|
||||
if (!noTimeout) {
|
||||
setTimeout(() => {
|
||||
this.fetchAll(stageId, true)
|
||||
}, 1000)
|
||||
return;
|
||||
}
|
||||
window.location.href = '/myshixuns/not_found'
|
||||
return;
|
||||
}
|
||||
|
||||
this._handleResponseData(response.data)
|
||||
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
readGameAnswer(resData) {
|
||||
// game.final_score = resData.final_score;
|
||||
if (resData.final_score) {
|
||||
var game = this.state.game;
|
||||
this.setState({
|
||||
game: update(game, {final_score: { $set: resData.final_score }}),
|
||||
grade: resData.grade
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
grade: resData.grade
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
closeTaskResultLayer() {
|
||||
this.setState({
|
||||
game: (this.state.game.status == 2 ? update(this.state.game, {
|
||||
isPassThrough: { $set: true },
|
||||
}) : this.state.game) ,
|
||||
currentGamePassed: false
|
||||
})
|
||||
}
|
||||
onRunChooseTestFinish(response) {
|
||||
const { test_sets, challenge_chooses_count, choose_correct_num, grade, experience, gold, had_submmit, next_game } = response;
|
||||
response.had_submmit = true; // 是否已提交
|
||||
const { game } = this.state;
|
||||
let currentGamePassed = false
|
||||
if (challenge_chooses_count === choose_correct_num) {
|
||||
game.status = 2;
|
||||
// game.isPassThrough = true
|
||||
game.next_game = next_game;
|
||||
|
||||
currentGamePassed = true;
|
||||
|
||||
|
||||
this._updateCostTime(true, true);
|
||||
}
|
||||
this.setState({
|
||||
choose_test_cases: response,
|
||||
grade: grade,
|
||||
|
||||
game,
|
||||
next_game,
|
||||
currentGamePassed: currentGamePassed,
|
||||
currentPassedGameGainGold: gold,
|
||||
currentPassedGameGainExperience: experience,
|
||||
})
|
||||
}
|
||||
initDisplayInterval = () => {
|
||||
const challenge = this.state.challenge
|
||||
if (this.showWebDisplayButtonTimeout) {
|
||||
window.clearTimeout(this.showWebDisplayButtonTimeout)
|
||||
}
|
||||
this.showWebDisplayButtonTimeout = window.setTimeout(() => {
|
||||
this.setState({ challenge: update(challenge,
|
||||
{
|
||||
showWebDisplayButton: { $set: false },
|
||||
})
|
||||
})
|
||||
this.showWebDisplayButtonTimeout = null
|
||||
}, 61 * 1000)
|
||||
|
||||
let remain = 60
|
||||
if (this.displayInterval) {
|
||||
window.clearInterval(this.displayInterval)
|
||||
}
|
||||
this.displayInterval = window.setInterval(() => {
|
||||
const button = $('#showWebDisplayButton');
|
||||
if (button.length) {
|
||||
button.html(`查看效果(${remain})`)
|
||||
if (remain == 0) {
|
||||
button.html('查看效果')
|
||||
}
|
||||
}
|
||||
if (remain == 0) {
|
||||
window.clearInterval(this.displayInterval)
|
||||
this.displayInterval = null
|
||||
return;
|
||||
}
|
||||
|
||||
remain -= 1;
|
||||
}, 1000)
|
||||
}
|
||||
language_display(data) {
|
||||
const { game, tomcat_url } = this.state;
|
||||
const challenge = Object.assign({}, this.state.challenge)
|
||||
if(challenge.isWeb && data.port != -1) {
|
||||
// var $result = $("#php_display");
|
||||
challenge.showWebDisplayButton = true; // ActionView处是否出现查看效果按钮
|
||||
this.initDisplayInterval()
|
||||
|
||||
const path = challenge.web_route || challenge.path
|
||||
const webDisplayUrl = `${tomcat_url}:${data.port}/${path}`
|
||||
challenge.webDisplayUrl = webDisplayUrl
|
||||
challenge.showLanguagePictrue = true; // 评测通过弹出层是否出现查看效果按钮
|
||||
}
|
||||
// else if(challenge.isAndroid && data.picture != 0){
|
||||
// // https://www.educoder.net/shixuns/qrcode?game_id=218589&_=1525571882782
|
||||
// $.ajax({
|
||||
// url: `/shixuns/qrcode?game_id=${game.id}`,
|
||||
// dataType: 'script'
|
||||
// });
|
||||
// challenge.showLanguagePictrue = true;
|
||||
// }
|
||||
else if(data.picture != 0){
|
||||
// 对应服务端erb文件为 _picture_display.html.erb
|
||||
// $.ajax({
|
||||
// url: "/users/picture_show?game_id="+data.picture,
|
||||
// cache: false,
|
||||
// dataType: 'script'
|
||||
// });
|
||||
|
||||
/**
|
||||
{
|
||||
"type": "image",
|
||||
"orignal_picture": [],
|
||||
"user_picture": [],
|
||||
"answer_picture": []
|
||||
}
|
||||
*/
|
||||
const url = `/tasks/${game.identifier}/picture_display.json`
|
||||
axios.get(url)
|
||||
.then((response) => {
|
||||
// response.data.type qrcode_str
|
||||
this.showEffectDisplay(response.data)
|
||||
})
|
||||
|
||||
challenge.showLanguagePictrue = true;
|
||||
}
|
||||
this.setState({
|
||||
challenge
|
||||
})
|
||||
}
|
||||
onRunCodeTestFinish(response) {
|
||||
console.log('onRunCodeTestFinish', response)
|
||||
const { test_sets, test_sets_count, test_sets_hidden_count, test_sets_public_count
|
||||
, had_test_count, had_passed_testsests_error_count, had_passed_testsests_hidden_count
|
||||
, had_passed_testsests_public_count, final_score, gold, experience, latest_output, status
|
||||
, had_done, score, tag_count, power, record, next_game, grade, picture,
|
||||
sets_error_count, last_compile_output, record_consume_time} = response;
|
||||
|
||||
const { game } = this.state;
|
||||
|
||||
const currentGamePassed = this.props.game !== 2 && status === 2
|
||||
|
||||
|
||||
|
||||
// 评测通过了,立即同步costTime
|
||||
currentGamePassed && this._updateCostTime(true, true);
|
||||
|
||||
|
||||
const output_sets = {
|
||||
"test_sets": test_sets,
|
||||
"test_sets_array": test_sets,
|
||||
"had_test_count": had_test_count || test_sets_count,
|
||||
"test_sets_count": test_sets_count,
|
||||
// "had_passed_testsests_error_count": had_passed_testsests_error_count,
|
||||
"had_passed_testsests_error_count": test_sets_count - sets_error_count,
|
||||
"test_sets_hidden_count": test_sets_hidden_count,
|
||||
"test_sets_public_count": test_sets_public_count,
|
||||
"had_passed_testsests_hidden_count": had_passed_testsests_hidden_count,
|
||||
"had_passed_testsests_public_count": had_passed_testsests_public_count
|
||||
};
|
||||
// if (output_sets && output_sets.test_sets) {
|
||||
// const test_sets_array = JSON.parse("[" + output_sets.test_sets + "]");
|
||||
// output_sets.test_sets_array = test_sets_array;
|
||||
// }
|
||||
|
||||
// 检查是否编译通过
|
||||
let compileSuccess = false;
|
||||
if (test_sets && test_sets.length) {
|
||||
test_sets.some((item) => {
|
||||
if (item.compile_success) {
|
||||
compileSuccess = true;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
compileSuccess && this.language_display(response);
|
||||
if (currentGamePassed) {
|
||||
game.status = 2;
|
||||
// game.isPassThrough = true
|
||||
game.next_game = next_game;
|
||||
} else {
|
||||
this.showDialog({
|
||||
contentText: <div>
|
||||
<div>评测未通过</div>
|
||||
<div>详情请参见“测试结果”</div>
|
||||
</div>,
|
||||
isSingleButton: true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
this.setState({
|
||||
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0), // 重置测试集展开状态
|
||||
currentGamePassed,
|
||||
currentPassedGameGainGold: gold,
|
||||
currentPassedGameGainExperience: experience,
|
||||
|
||||
output_sets,
|
||||
game,
|
||||
next_game,
|
||||
|
||||
latest_output: last_compile_output,
|
||||
record: record_consume_time,
|
||||
grade,
|
||||
had_done,
|
||||
|
||||
})
|
||||
}
|
||||
resetTestSetsExpandedArray = () => {
|
||||
this.setState({
|
||||
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0), // 重置测试集展开状态
|
||||
})
|
||||
}
|
||||
|
||||
testSetUnlock() {
|
||||
const { game, challenge } = this.state;
|
||||
const url = `/tasks/${game.identifier}/check_test_sets.json`
|
||||
axios.get(url, {
|
||||
// withCredentials: true,
|
||||
})
|
||||
.then((response) => {
|
||||
// TODO status -2 重复操作,直接解锁
|
||||
if (response.data.test_sets == -1) {
|
||||
console.error('testSetUnlock失败!')
|
||||
this.showSnackbar(response.data.message)
|
||||
return;
|
||||
} else {
|
||||
// 被扣除的金币,是负数
|
||||
const deltaScore = -challenge.score * 5;
|
||||
// output_sets
|
||||
let { output_sets } = this.state;
|
||||
output_sets = Object.assign({}, output_sets);
|
||||
// const test_sets_array = JSON.parse("[" + response.data.test_sets + "]");
|
||||
output_sets.test_sets_array = response.data.test_sets;
|
||||
this.setState({
|
||||
output_sets: output_sets,
|
||||
grade: this.state.grade + deltaScore,
|
||||
game : update(game, {test_sets_view: { $set: true }}),
|
||||
testSetsExpandedArray: testSetsExpandedArrayInitVal.slice(0)
|
||||
})
|
||||
this.handleGdialogClose();
|
||||
}
|
||||
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
handleSnackbarClose() {
|
||||
this.setState({
|
||||
snackbarOpen: false,
|
||||
snackbarVertical: '',
|
||||
snackbarHorizontal: '',
|
||||
})
|
||||
}
|
||||
// 全局的snackbar this.props.showSnackbar调用即可
|
||||
showSnackbar(text, vertical, horizontal) {
|
||||
this.setState({
|
||||
snackbarOpen: true,
|
||||
snackbarText: text,
|
||||
snackbarVertical: vertical,
|
||||
snackbarHorizontal: horizontal,
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
TODO 写成HOC组件,更好复用
|
||||
全局的Dialog this.props.showDialog调用即可
|
||||
@param contentText dialog显示的提示文本
|
||||
@param callback 确定按钮回调方法
|
||||
@param moreButtonsRender 除了“确定”、“取消”按钮外的其他按钮
|
||||
@param okButtonText “确定”按钮显示文本,如 继续查看
|
||||
*/
|
||||
showDialog(params) {
|
||||
const { contentText, callback, moreButtonsRender, okButtonText, isSingleButton } = params;
|
||||
|
||||
this.dialogOkCallback = callback;
|
||||
this.moreButtonsRender = moreButtonsRender
|
||||
this.okButtonText = okButtonText;
|
||||
this.isSingleButton = isSingleButton;
|
||||
this.setState({
|
||||
gDialogOpen: true,
|
||||
gDialogContentText: contentText
|
||||
})
|
||||
}
|
||||
onGdialogOkBtnClick() {
|
||||
|
||||
this.dialogOkCallback && this.dialogOkCallback();
|
||||
// this.setState({
|
||||
// gDialogOpen: true
|
||||
// })
|
||||
}
|
||||
handleGdialogClose = () => {
|
||||
this.setState({
|
||||
gDialogOpen: false
|
||||
})
|
||||
}
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<TPIContext.Provider
|
||||
value={{
|
||||
...this.props,
|
||||
...this.state,
|
||||
resetTestSetsExpandedArray: this.resetTestSetsExpandedArray,
|
||||
onRunCodeTestFinish: this.onRunCodeTestFinish,
|
||||
onRunChooseTestFinish: this.onRunChooseTestFinish,
|
||||
testSetUnlock: this.testSetUnlock,
|
||||
|
||||
onTestSetHeaderClick: this.onTestSetHeaderClick,
|
||||
|
||||
readGameAnswer: this.readGameAnswer,
|
||||
|
||||
onShowPrevStage: this.onShowPrevStage,
|
||||
onShowNextStage: this.onShowNextStage,
|
||||
|
||||
praisePlus: this.praisePlus,
|
||||
onGamePassed: this.onGamePassed,
|
||||
closeTaskResultLayer: () => this.closeTaskResultLayer(),
|
||||
|
||||
onPathChange: this.onPathChange,
|
||||
updateChallengePath: this.updateChallengePath,
|
||||
|
||||
showSnackbar: this.showSnackbar,
|
||||
showDialog: this.showDialog,
|
||||
handleGdialogClose: () => this.handleGdialogClose(),
|
||||
|
||||
onShowUpdateDialog: this.onShowUpdateDialog,
|
||||
updateDialogClose: this.updateDialogClose,
|
||||
|
||||
match: this.props.match
|
||||
}}
|
||||
>
|
||||
<Dialog
|
||||
id="tpi-dialog"
|
||||
open={this.state.gDialogOpen}
|
||||
disableEscapeKeyDown={true}
|
||||
onClose={() => this.handleGdialogClose()}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">{"提示"}</DialogTitle>
|
||||
<DialogContent id="dialog-content">
|
||||
<DialogContentText id="alert-dialog-description" style={{textAlign: 'center'}}>
|
||||
{this.state.gDialogContentText}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
{/* mb20 加了有样式问题 */}
|
||||
<DialogActions className={""} id="dialog-actions">
|
||||
{ this.isSingleButton ? <div className="task-popup-submit clearfix"
|
||||
style={{ textAlign: 'center', 'margin-bottom': '14px'}}>
|
||||
<a className="task-btn task-btn-orange"
|
||||
onClick={this.handleGdialogClose}
|
||||
>知道啦</a>
|
||||
</div> :
|
||||
<React.Fragment>
|
||||
<Button onClick={() => this.handleGdialogClose()} color="primary"
|
||||
className={`${classes.button} ${classes.buttonGray} ${classes.borderRadiusNone}`}>
|
||||
关闭
|
||||
</Button>
|
||||
<Button variant="raised" className={`${classes.button} ${classes.borderRadiusNone}`}
|
||||
onClick={() => this.onGdialogOkBtnClick() } color="primary" autoFocus>
|
||||
{ this.okButtonText ? this.okButtonText : '确定' }
|
||||
</Button>
|
||||
</React.Fragment> }
|
||||
{this.moreButtonsRender && this.moreButtonsRender()}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Snackbar
|
||||
className={"rootSnackbar"}
|
||||
open={this.state.snackbarOpen}
|
||||
autoHideDuration={3000}
|
||||
anchorOrigin={{ vertical: this.state.snackbarVertical || 'top'
|
||||
, horizontal: this.state.snackbarHorizontal || 'center' }}
|
||||
onClose={() => this.handleSnackbarClose()}
|
||||
transition={Fade}
|
||||
SnackbarContentProps={{
|
||||
'aria-describedby': 'message-id',
|
||||
}}
|
||||
resumeHideDuration={2000}
|
||||
message={<span id="message-id">{this.state.snackbarText}</span>}
|
||||
/>
|
||||
{this.props.children}
|
||||
</TPIContext.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CNotificationHOC() (withStyles(styles) (TPIContextProvider));
|
||||
|
||||
|
||||
|
||||
182
public/react/src/forge/Activity/Activity.js
Normal file
@@ -0,0 +1,182 @@
|
||||
import React , { Component } from 'react';
|
||||
import { Dropdown , Menu , Icon , Pagination} from 'antd';
|
||||
import { Link } from 'react-router-dom';
|
||||
import moment from 'moment';
|
||||
import '../css/index.css';
|
||||
import '../Branch/branch.css';
|
||||
import './activity.css';
|
||||
|
||||
import ActivityItem from './ActivityItem';
|
||||
import axios from 'axios';
|
||||
const LIMIT = 15;
|
||||
const ARRAY = [
|
||||
{
|
||||
id:1,
|
||||
name:'1天'
|
||||
},
|
||||
{
|
||||
id:3,
|
||||
name:'3天'
|
||||
},
|
||||
{
|
||||
id:7,
|
||||
name:'1周'
|
||||
},
|
||||
{
|
||||
id:30,
|
||||
name:'1个月'
|
||||
}
|
||||
]
|
||||
const dataformat="YYYY-MM-DD HH:mm";
|
||||
|
||||
class Activity extends Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state={
|
||||
time:'1',
|
||||
type:undefined,
|
||||
state:undefined,
|
||||
page:1,
|
||||
|
||||
data:undefined,
|
||||
project_trends:undefined
|
||||
}
|
||||
}
|
||||
componentDidMount=()=>{
|
||||
const { time,type,status,page } = this.state;
|
||||
this.getInfo(time,type,status,page);
|
||||
}
|
||||
|
||||
getInfo =(time,type,status,page)=>{
|
||||
const { projectsId } = this.props.match.params;
|
||||
const url = `/projects/${projectsId}/project_trends.json`;
|
||||
axios.get(url,{
|
||||
params:{
|
||||
time,type,status,page
|
||||
}
|
||||
}).then(result=>{
|
||||
if(result){
|
||||
this.setState({
|
||||
data:result.data,
|
||||
project_trends:result.data.project_trends
|
||||
})
|
||||
}
|
||||
}).catch(error=>{
|
||||
console.log(error);
|
||||
})
|
||||
}
|
||||
|
||||
// 切换周期
|
||||
changeTime=(e)=>{
|
||||
this.setState({
|
||||
time:e.key
|
||||
})
|
||||
const { type,status,page } = this.state;
|
||||
this.getInfo(e.key,type,status,page);
|
||||
}
|
||||
//筛选
|
||||
changeTrends=(type,status)=>{
|
||||
this.setState({
|
||||
type,status
|
||||
})
|
||||
const {time,page}=this.state;
|
||||
this.getInfo(time,type,status,page);
|
||||
}
|
||||
// 分页
|
||||
ChangePage=(page)=>{
|
||||
this.setState({
|
||||
page
|
||||
})
|
||||
const { time,type,status } = this.state;
|
||||
this.getInfo(time,type,status,page);
|
||||
}
|
||||
render(){
|
||||
const { time , data , page , project_trends } = this.state;
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
{
|
||||
ARRAY && ARRAY.map((item,key)=>{
|
||||
return(
|
||||
<Menu.Item key={item.id} onClick={this.changeTime}>{item.name}</Menu.Item>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
|
||||
const second_per = (parseInt(data && data.close_issues_count)/parseInt(data && data.issues_count)*100)+'%';
|
||||
|
||||
const third_per = (parseInt(data && data.close_issues_count)/parseInt(data && data.issues_count)*100)+'%';
|
||||
const fourth_per = (parseInt(data && data.open_issues_count)/parseInt(data && data.issues_count)*100)+'%';
|
||||
return(
|
||||
<div className="main">
|
||||
|
||||
<div className="normalBox">
|
||||
<div class="normalBox-title">概览</div>
|
||||
<div className="orderInfo">
|
||||
<div>
|
||||
<div className="percentLine prPercent">
|
||||
<p className="percent_purple" style={{width:'100%'}}></p>
|
||||
<p className="percent_green resetStyle" style={{width:`${second_per}`}}></p>
|
||||
</div>
|
||||
<span>{data && data.pr_all_count}合并请求</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="percentLine">
|
||||
<p className="percent_red" style={{width:`${third_per}`}}></p>
|
||||
<p className="percent_green" style={{width:`${fourth_per}`}}></p>
|
||||
</div>
|
||||
<span>{data && data.issues_count}工单</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="percentBox">
|
||||
<li>
|
||||
<span className="purple">{data && data.pr_count}</span>
|
||||
<span className="change" onClick={()=>this.changeTrends("PullRequest","close")}>已合并请求</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="green">{data && data.new_pr_count}</span>
|
||||
<span className="change" onClick={()=>this.changeTrends("PullRequest","create")}>新合并请求</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="red">{data && data.close_issues_count}</span>
|
||||
<span className="change" onClick={()=>this.changeTrends("Issue","close")}>已关闭工单</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className="green">{data && data.open_issues_count}</span>
|
||||
<span className="change" onClick={()=>this.changeTrends("Issue","create")}>创建的工单</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="df trendsTop mt20">
|
||||
<div className="branchDropdown f-wrap-alignCenter">
|
||||
<span className="color-grey-9 mr3">周期:</span>
|
||||
<Dropdown overlay={menu} trigger={['click']} placement="bottomLeft">
|
||||
<a className="ant-dropdown-link">
|
||||
{time} <Icon type="down" />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div className="activity_list">
|
||||
{
|
||||
project_trends && project_trends.map((item,key)=>{
|
||||
return(
|
||||
<ActivityItem item={item} {...this.props} ></ActivityItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
{
|
||||
data && data.project_trends_size > 0 && data.project_trends_size > LIMIT &&
|
||||
<div className="pageDIV">
|
||||
<Pagination showQuickJumper defaultCurrent={page} total={data && data.project_trends_size} pageSize={LIMIT} onChange={this.ChangePage}></Pagination>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default Activity;
|
||||
44
public/react/src/forge/Activity/ActivityItem.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React , { Component } from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
import './activity.css';
|
||||
import { getImageUrl } from 'educoder';
|
||||
|
||||
|
||||
class ActivityItem extends Component{
|
||||
render(){
|
||||
const { projectsId } = this.props.match.params;
|
||||
const { item } = this.props;
|
||||
return(
|
||||
<div className="activity_item">
|
||||
<div className="flex1">
|
||||
{/* 如果是版本发布 */}
|
||||
{ item.trend_type==="VersionRelease"?
|
||||
<p className="itemLine">
|
||||
<Link to={`/projects/${projectsId}/version`} className="color-blue font-16">{item.name}</Link>
|
||||
<span className="activity_type">{item.trend_type}</span>
|
||||
</p >
|
||||
:
|
||||
// 如果是工单
|
||||
item.trend_type==="Issue"?
|
||||
<p className="itemLine">
|
||||
<Link to={`/projects/${projectsId}/orders/${item.trend_id}/detail`} className="color-blue font-16">{item.name}</Link>
|
||||
<span className="activity_type">{item.trend_type}</span>
|
||||
</p >
|
||||
:
|
||||
// 如果是合并请求
|
||||
<p className="itemLine">
|
||||
<Link to={`/projects/${projectsId}/merge/${item.trend_id}/Messagecount`} className="color-blue font-16">{item.name}</Link>
|
||||
<span className="activity_type">{item.trend_type}</span>
|
||||
</p >
|
||||
}
|
||||
<p className="itemLine mt15">
|
||||
<img alt="" src={getImageUrl(`images/${item.user_avatar}`)} className="createImage"/>
|
||||
<span className="mr20">{item.user_name}</span>
|
||||
{ item.created_at && <span className="color-grey-9">创建于<span className="ml2 color-grey-6">{item.created_at}</span></span>}
|
||||
</p >
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default ActivityItem;
|
||||
108
public/react/src/forge/Activity/activity.css
Normal file
@@ -0,0 +1,108 @@
|
||||
.trendsTop{
|
||||
padding-bottom: 15px;
|
||||
border-bottom:1px solid #ddd;
|
||||
}
|
||||
|
||||
.pageDIV{
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.orderInfo{
|
||||
padding:15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.orderInfo > div{
|
||||
width: 45%;
|
||||
}
|
||||
.percentLine{
|
||||
width: 100%;
|
||||
display:flex;
|
||||
height: 8px;
|
||||
border-radius: 2px;
|
||||
background: #888;
|
||||
position: relative;
|
||||
}
|
||||
.percent_green{
|
||||
background-color: #6cc644;
|
||||
color: #6cc644;
|
||||
}
|
||||
.percent_purple{
|
||||
background-color: #6e5494;
|
||||
color: #6e5494;
|
||||
}
|
||||
.percent_red{
|
||||
background-color: #d95c5c;
|
||||
}
|
||||
.green{
|
||||
color: #6cc644;
|
||||
}
|
||||
.purple{
|
||||
color: #6e5494;
|
||||
}
|
||||
.red{
|
||||
color: #d95c5c;
|
||||
}
|
||||
.percentBox{
|
||||
display: flex;
|
||||
border-top: 1px solid #f4f4f4;
|
||||
}
|
||||
.percentBox > li{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
border-right: 1px solid #f4f4f4;
|
||||
padding:20px 0px;
|
||||
color: #4183c4;
|
||||
}
|
||||
.percentBox > li:last-child{
|
||||
border-right: none;
|
||||
}
|
||||
.activity_list .activity_item{
|
||||
border-bottom: 1px solid #f4f4f4;
|
||||
padding:15px 0px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.prPercent > p{
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top:0px;
|
||||
left: 0px;
|
||||
z-index: 0;
|
||||
}
|
||||
.prPercent > p.resetStyle{
|
||||
left: unset;
|
||||
right: 0px;
|
||||
z-index: 1;
|
||||
}
|
||||
.itemLine{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.activity_type{
|
||||
display: block;
|
||||
padding:0px 5px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
background: #6cc644;
|
||||
margin-left: 10px;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.createImage{
|
||||
margin-right: 5px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.change{
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
.change:hover{
|
||||
color: #4183c4;
|
||||
}
|
||||
36
public/react/src/forge/Branch/CloneAddress.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React , { Component } from 'react';
|
||||
import { Dropdown , Icon , Menu } from 'antd';
|
||||
|
||||
import "./branch.css"
|
||||
|
||||
class CloneAddress extends Component{
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
// 点击按钮复制功能
|
||||
jsCopy=()=>{
|
||||
var e = document.getElementById("copy_rep_content");
|
||||
e.select();
|
||||
document.execCommand("Copy");
|
||||
this.props.showNotification('复制成功');
|
||||
}
|
||||
|
||||
render(){
|
||||
const { http_url , downloadUrl } = this.props;
|
||||
return(
|
||||
<div className="gitAddressClone">
|
||||
<span className={"addressType active"} onClick={()=>this.changeAddress("http")}>HTTP</span>
|
||||
{/* <span className={address ==="ssh" ? "addressType active":"addressType"} onClick={()=>this.changeAddress("ssh")}>SSH</span> */}
|
||||
<input type="text" id="copy_rep_content" value={ http_url }/>
|
||||
<span onClick={()=>this.jsCopy()}><i className="iconfont icon-fuzhi"></i></span>
|
||||
<span>
|
||||
<Dropdown overlay={downloadUrl} trigger={['click']} placement="bottomRight">
|
||||
<a className="ant-dropdown-link">
|
||||
<Icon type="cloud-download" className="font-18 fl"/>
|
||||
</a>
|
||||
</Dropdown></span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default CloneAddress;
|
||||
33
public/react/src/forge/Branch/SelectBranch.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React , { Component } from 'react';
|
||||
import { Dropdown , Icon , Menu } from 'antd';
|
||||
|
||||
import "./branch.css"
|
||||
|
||||
class SelectBranch extends Component{
|
||||
|
||||
render(){
|
||||
const { branchs , branch , changeBranch } = this.props;
|
||||
const menu = (
|
||||
<Menu>
|
||||
{
|
||||
branchs && branchs.map((item,key)=>{
|
||||
return(
|
||||
<Menu.Item key={item.index} onClick={(value)=>changeBranch(value)}>{item.name}</Menu.Item>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Menu>
|
||||
);
|
||||
return(
|
||||
<div className="branchDropdown f-wrap-alignCenter">
|
||||
<span className="color-grey-9 mr3"><i className="iconfont icon-fenzhi font-20 color-grey-6 mr3"></i>分支:</span>
|
||||
<Dropdown overlay={menu} trigger={['click']} placement="bottomLeft">
|
||||
<a className="ant-dropdown-link">
|
||||
{branch} <Icon type="down" />
|
||||
</a>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default SelectBranch;
|
||||
9
public/react/src/forge/Branch/branch.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.branchDropdown{
|
||||
border:1px solid #eee;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding:0px 10px;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
}
|
||||
BIN
public/react/src/forge/Images/1.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
public/react/src/forge/Images/2.png
Normal file
|
After Width: | Height: | Size: 214 B |
BIN
public/react/src/forge/Images/3.png
Normal file
|
After Width: | Height: | Size: 382 B |
BIN
public/react/src/forge/Images/4.png
Normal file
|
After Width: | Height: | Size: 585 B |
BIN
public/react/src/forge/Images/5.png
Normal file
|
After Width: | Height: | Size: 451 B |
BIN
public/react/src/forge/Images/6.png
Normal file
|
After Width: | Height: | Size: 621 B |
BIN
public/react/src/forge/Images/7.png
Normal file
|
After Width: | Height: | Size: 674 B |
BIN
public/react/src/forge/Images/array.png
Normal file
|
After Width: | Height: | Size: 266 B |
BIN
public/react/src/forge/Images/banner_list.png
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
public/react/src/forge/Images/banner_sub.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
public/react/src/forge/Images/banner_team.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |