init project

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

107
public/react/src/App.css Normal file
View 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
View 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) ;

View 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);
});

View 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:
'网络异常,请检测网络后重试。',
})
});
}

View 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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

View File

@@ -0,0 +1,5 @@
import md5 from 'md5';
export function setmiyah(logins){
const opens ="79e33abd4b6588941ab7622aed1e67e8";
return md5(opens+logins);
}

View 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 // 普通用户

View 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 + "分";
}

View 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()));

View 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)
}
}

View 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浏览器
}
}

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

View 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;');
}

View 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)}`)
}

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

View 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>
)
}
}
}
}

View 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];
}

View 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", ]

View 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];
}

View 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, "&amp;");
s = s.replace(/</g, "&lt;");
s = s.replace(/>/g, "&gt;");
s = s.replace(/ /g, "&nbsp;");
s = s.replace(/\'/g, "&#39;");//IE下不支持实体名称
s = s.replace(/\"/g, "&quot;");
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>)
}

View 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

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

View 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);
// }
// });
// }
// }

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

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

View 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();
}
});
}

View 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)
})
*/

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

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

View 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>
)
}
}
}
}

View File

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

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

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

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

View 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%;
}

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

View File

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

View File

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

View 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

View 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

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

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

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

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

View 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'

View 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)

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

View 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';

View 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>
````

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

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

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

View 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';

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

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

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

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

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

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

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

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

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

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

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

View 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

View 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)
})
}

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

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

View 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&#10;UY6jSgy0Q6TOH7lBc5POxSbAzMXgJnCD8EdLQ/pVmSGz+/52pcdDo46iEWGw&#10;1GnZ5XLZnVP649dJ/viVTbJJNskm2eRYJovyJdJr1V7k9X6XsRF5j7g56lXs&#10;oduuCzuuSYkM2Ml0S30783M9VfWM7fL471vZp1k+RQo89Nh5Ycc16eFDi08X&#10;/4yVRG1cf4rVXSRd5GOWVqszHpr0vPvCDm8ynmQ0xuWXi29kbCflHgp97H9d&#10;2CFNiodM4ekG+FXSCiCmyo9tOYhL/+fCDmkSVoQynP+YKgWgpQRMa+FpMQDH&#10;3INo97n7wo5qMjELLtee1whGkRgaC0bF7s3cRqQFZtTrzgs7qklh5WIx3txw&#10;t5tvl7FxA2CtLnnn7SLRd/ZQt+vCjmyi1/5cJtW67J/4NAPKtOEFcwHC07Jk&#10;n3OlvKKyeASAlEsNGQWJqzgA/gDvwvsd1xNDMuPYl8lylXjtgVNRi6kClDEY&#10;Y/V28cUUTi4tF/ksuzTWS+b8r1xZjDXcLA13hxRJto9c2YgAxOoZ+A/uGhbf&#10;/cyoLB6AG4BSJ2K4lJqotVYfQCTXWwb0YF5u1AzjXyZwBdQ7uAALBS6BX6UB&#10;2/U0hwEdBTWNpBKMxV0XdlgTkIRiVIBSSk8ZiwT09g2ZvFetQBhA45H14GZF&#10;Lqs3H0vRJ4ASInEKjVC0QZEIhgDOUFOa8EXqPiX8TFD6CxPgGHwoMuTurkea&#10;q5HvYtUavyKlNzKP7Yo5Kl9UAYkPF6bYSERNTSL6XPk8Prr+PN3AFhC+uy7s&#10;sCaAJ/VtAZLgCVmu514xBYST1YyAtSzd/GayaA2uyZrZqLvhWG3qTbMqhJMi&#10;nVKB9t2uCzuuSdnijcCkUE/ZIWllUZmWsic7dQPSAulqjsqtSASIBRY+1Z1h&#10;iCzptUxrQd1e+AT8C/83knvw77KTCTk/CIT1ODQxXZKGxVBffCI9W9WdNheJ&#10;soldHcoiUgcP+OJdx2bQu6XORJKGm/1yDUWukl4mZ3MzqjS9RaW0Gp5CBzOJ&#10;1cdrQF0Zs49tufLG/uMjWN2N5Njq2nxkFnhHuVT2lKBRFmQfe0nQn5T/+lhp&#10;MtbqksLfrAvZRDJ/lgXTbe+FHdbEsiA27UblGailrCgB9BaS+FvP3npHfR4J&#10;+NWkR2C6QakK+qewOEqEMrF0YNRia7gd/7v89yYoGiUIO2jkFQ6+hHhkudSg&#10;RFLKZe5BxT5rF98m50nx5gxAdQeazfSrdmILEvTsyVxpcxZZu3ipPayCUhJQ&#10;/eHdpafrWxLXUUtQsoAsALYPHJPM+V+KIurKyHkwnYxd2CRAY13Iu4xpHqkG&#10;lV1upcmmWuuKYyKpHDjaFJXy9YwU4E9C7dq0i1tWrb9NzpYrt+428qOa1N9e&#10;RkToc20FpO5slOzo32UHE/aStEYVNHMArJFV25e79d2Ugr9fqIyFvRd2XJPp&#10;RmHCd5exmMJSoiwCax1PBmu2bwzc5dpnHHuZhPNUg/NPoUZylNXT/MIJCjZ5&#10;TzJG62cuWYPdNNgOudI9AtOiR2YkE3O91UqD2hSiTJqsebLvwg5rAu5KGbYn&#10;ask7yiL7CzJWK7ZuaLXCplIQysj/8rHPskVsln27AdfaJdH5KxGcrV7POCYb&#10;5w/nr0T4VoJRyAcrI5aZtQ2ft8gFZGIhc355qT0LCH/ZzTbRytYbO5Vp5hAd&#10;NhBedzZt2+Ve0jeOUR/TtW1krKxWutaHnQQp0hCvYlXUzgs7rkngCBlCbmw5&#10;BDWuzL8GpvGAAx2P3kW063Ze2EFNWC5OdVRSfRP2xQ1vJGayXss2mvEz9bHF&#10;t58sEtsLu7QNdsAmT+5GSp82RIGn1Le5MbSZTDf1nLSnUMPXnSM6FL7uUcHe&#10;i6lKKcGnfqbD/IWJndDjpH3UYu1xcxiMzY6WmxaePD3Ek5FZtP/VJCDkrCps&#10;KHnxxFBNkgB+NSrJFXscuUj8Mnmdrk0EMSqEQjy340Lj1kojh8/y4GZip2sR&#10;m3QwslBUiA372oPYAdv1lRtDv5pY10dvxhOQKOtJa060+vWMFctqPhLyUOu/&#10;TBCVyWZZsW8mRH9N04kjV0XlnaPyNxMUgRw4QXnIwQDBphmBb+wIPNKCyyeG&#10;fjFZm/1Il23qkSLB5NOFvw/QzgQ0ccPZBhIzjm0m0/obE/3MOfy7oRmPVs2+&#10;tlyAN2Afm5pHm+SbwYLTS7AJ85YHZ7xtFDfNJk9sr3K7Nptkk2ySTX6EyT81&#10;+NIc7MmVogAAAABJRU5ErkJggg==&#10;">
</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]`

View File

@@ -0,0 +1,4 @@
import React from 'react';
const TPIContext = React.createContext('tpi')
export default TPIContext;

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

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

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

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

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 621 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Some files were not shown because too many files have changed in this diff Show More