Merge branch 'dev_aliyun' of https://bdgit.educoder.net/Hjqreturn/educoder into dev_aliyun

dev_jupyter
daiao 5 years ago
commit 9dc6c68a09

@ -78,7 +78,6 @@
"react-dev-utils": "^5.0.0",
"react-dom": "^16.9.0",
"react-hot-loader": "^4.0.0",
"react-infinite-scroller": "^1.2.4",
"react-loadable": "^5.3.1",
"react-monaco-editor": "^0.25.1",
"react-player": "^1.11.1",
@ -176,6 +175,7 @@
"compression-webpack-plugin": "^1.1.12",
"concat": "^1.0.3",
"happypack": "^5.0.1",
"mockjs": "^1.1.0",
"node-sass": "^4.12.0",
"reqwest": "^2.0.5",
"webpack-bundle-analyzer": "^3.0.3",

@ -82,8 +82,10 @@ export function initAxiosInterceptors(props) {
// 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="https://test-jupyterweb.educoder.net"
//proxy="http://192.168.2.63:3001"
// 在这里使用requestMap控制避免用户通过双击等操作发出重复的请求

@ -17,10 +17,14 @@ const PathsNew = Loadable({
loader: () => import('./PathNew'),
loading:Loading,
})
// const Statistics = Loadable({
// loader: () => import('./SchoolStatistics/Statistics'),
// loading:Loading
// })
const Statistics = Loadable({
loader: () => import('./SchoolStatistics/Statistics'),
loading:Loading
})
loader: () => import('./statics'),
loading: Loading
});
const ShixunPaths = Loadable({
loader: () => import('./ShixunPaths'),

@ -0,0 +1,80 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2020-01-14 13:39:12
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-14 16:30:05
*/
import './index.scss';
import React, { useRef, useState, useEffect } from 'react';
import { Table } from 'antd';
import ReactDom from 'react-dom';
const DisplayTableData = (props) => {
const {columns, datas, fetchData, total} = props;
let tableEl = useRef(null);
const [loading, setLoading] = useState(false);
const renderFooter = (obj = {}) => {
const {course_count, student_count, choice_shixun_num, choice_shixun_frequency, total} = obj;
if (!obj) return ''
else {
return (
<ul className="footer_list">
<li className="footer_item footer-total">总计</li>
<li className="footer_name">{total || '-'}</li>
<li className="footer_item">{course_count || '-'}</li>
<li className="footer_item">{student_count || '-'}</li>
<li className="footer_item">{choice_shixun_num || '-'}</li>
<li className="footer_item">{choice_shixun_frequency || '-'}</li>
</ul>
)
}
}
useEffect(() => {
const table = ReactDom.findDOMNode(tableEl);
// console.log(table);
const tableBody = table.querySelector('.ant-table-body');
let _scrollTop = 0;//保存上次滚动距离
let isRun = false;//是否执行查询
tableBody.addEventListener('scroll', () => {
if(tableBody.scrollTop === 0 ){
_scrollTop = 0;
}
// 上一次滚动高度与当前滚动高度不同则是纵向滚动
if (_scrollTop !== tableBody.scrollTop) {
//是否滑动到距离底部40px的位置
const scorll = _scrollTop >= tableBody.scrollHeight-tableBody.clientHeight-40;
//isRun为true时 代表已经执行查询
if(isRun && scorll){
return;
}
//_scrollTop < tableBody.scrollTop 判断是否向下滑动
isRun = _scrollTop < tableBody.scrollTop && scorll;
//保存当前滚动位置
_scrollTop = tableBody.scrollTop;
if (isRun) {
fetchData && fetchData();
}
}
})
}, []);
return (
<Table
className='static_table'
rowKey={record => record.id}
columns={columns}
dataSource={datas}
pagination={false}
loading={loading}
scroll={{y: 500}}
ref={(ref)=>tableEl=ref}
footer={total ? () => renderFooter(total) : ''}
/>
);
}
export default DisplayTableData;

@ -0,0 +1,43 @@
/*
* @Description: 数字及文字提示
* @Author: tangjiang
* @Github:
* @Date: 2020-01-10 10:26:57
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-10 11:15:28
*/
import './index.scss';
import React from 'react';
import { Tooltip } from 'antd';
const numberal = require('numeral');
const StaticNumberAndTxt = ({
count = 0, // 总数
txt, // 文字描述
type = 'tishi1', // 字体类型
desc // 描述信息
}) => {
const formatNumber = (value, format = '0,0') => {
return numberal(value).format(format);
}
const _classes = `iconfont icon-${type} icon`;
return (
<div className="static-flex-item">
<span className="item-count">{formatNumber(count)}</span>
<span className="item-txt">
{txt}
<Tooltip
placement='bottom'
title={desc}
overlayClassName='tool-clazz'
>
<span className={_classes}></span>
</Tooltip>
</span>
</div>
);
}
export default StaticNumberAndTxt;

@ -0,0 +1,339 @@
/*
* @Description: 实践课程统计页面
* @Author: tangjiang
* @Github:
* @Date: 2020-01-10 09:33:45
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-14 17:01:32
*/
import './index.scss';
import React, { useEffect } from 'react';
import StaticNumberAndTxt from './StaticNumberAndTxt';
import DisplayTableData from './DisplayTableData';
import { Tabs, Tooltip } from 'antd';
import { connect } from 'react-redux';
import moment from 'moment';
import actions from '../../../redux/actions';
const { TabPane } = Tabs;
const App = (props) => {
const {
subject_info,
other_info,
total,
staticList,
changeParams,
initTotal
} = props;
// const [datas, setDatas] = useState([]);
// const [sortedInfo, setSortedInfo] = useState({});
// console.log(props);
const {pathId} = props.match.params;
const columns = [
{
title: '序号',
dataIndex: 'id',
key: 'id',
render: (text, record, i) => i + 1,
width: 100,
align: 'center'
},
{
title: '使用单位',
// key: 'school_name',
dataIndex: 'school_name',
// width: 300,
className: 'overflow_hidden',
align: 'center'
},
{
// title: '使用课堂/个',
title: () => (<Tooltip title="将该课程使用到课堂的数量">使用课堂</Tooltip>),
// key: 'course_count',
width: 150,
dataIndex: 'course_count',
align: 'center',
sorter: (a, b) => a.course_count - b.course_count,
// sortOrder: sortedInfo.columnKey === 'age' && sortedInfo.order
// sorter: (a, b) => true,
// sorter: (a, b) => a.age - b.age
},
{
title: () => (<Tooltip title="课堂的学生总数(去掉重复)">课堂学生</Tooltip>),
// key: 'student_count',
width:150,
dataIndex: 'student_count',
align: 'center',
sorter: (a, b) => a.student_count - b.student_count,
// sorter: (a, b) => a.age - b.age
},
{
title: () => (<Tooltip title="选用该课程实训的个数(去重)">选用实训/</Tooltip>),
width: 150,
// key: 'choice_shixun_num',
dataIndex: 'choice_shixun_num',
align: 'center',
sorter: (a, b) => a.choice_shixun_num - b.choice_shixun_num,
// sorter: (a, b) => a.age - b.age
},
{
title: () => (<Tooltip title="选用该课程实训的次数">选用实训/</Tooltip>),
width: 150,
// key: 'choice_shixun_frequency',
dataIndex: 'choice_shixun_frequency',
align: 'center',
sorter: (a, b) => a.choice_shixun_frequency - b.choice_shixun_frequency,
// sorter: (a, b) => a.bbb - b.bbb
}
];
const sxColumns = [
{
title: '序号',
dataIndex: 'id',
render: (text, record, i) => i + 1,
width: 60,
align: 'center'
}, {
title: '章节',
dataIndex: 'stage',
width: 80,
align: 'center'
},
{
title: '实训名称',
dataIndex: 'shixun_name',
align: 'center',
// ellipsis: true
},
{
title: '关卡数',
dataIndex: 'challenge_count',
width: 100,
align: 'center'
},
{
title: '使用课堂',
dataIndex: 'course_count',
width: 110,
align: 'center',
sorter: (a, b) => a.course_count - b.course_count
},
{
title: '使用单位',
dataIndex: 'school_count',
width: 110,
align: 'center',
sorter: (a, b) => a.school_count - b.school_count
},
{
title: '使用人数',
dataIndex: 'used_count',
width: 110,
align: 'center',
sorter: (a, b) => a.used_count - b.used_count
},
{
title: '通关人数',
dataIndex: 'passed_count',
width: 110,
align: 'center',
sorter: (a, b) => a.passed_count - b.passed_count
},
{
title: '评测次数',
dataIndex: 'evaluate_count',
width: 110,
align: 'center',
sorter: (a, b) => a.evaluate_count - b.evaluate_count
},
{
title: '通关平均时间',
dataIndex: 'passed_ave_time',
width: 140,
align: 'center',
render: (text) => (text && moment(text).format('HH:mm:ss')) || '-',
sorter: (a, b) => a.passed_ave_time - b.passed_ave_time
}
];
const stColumns = [
{
title: '序号',
dataIndex: 'id',
render: (text, record, i) => i + 1,
width: 60,
align: 'center'
},
{
title: '姓名',
dataIndex: 'username',
align: 'center'
},
{
title: '通关实训数',
dataIndex: 'passed_myshixun_count',
align: 'center',
sorter: (a, b) => a.passed_myshixun_count - b.passed_myshixun_count
},
{
title: '完成关卡',
dataIndex: 'passed_games_count',
align: 'center',
sorter: (a, b) => a.passed_games_count - b.passed_games_count
},
{
title: '代码行',
dataIndex: 'code_line_count',
align: 'center',
sorter: (a, b) => a.code_line_count - b.code_line_count
},
{
title: '评测次数',
dataIndex: 'evaluate_count',
align: 'center',
sorter: (a, b) => a.evaluate_count - b.evaluate_count
},
{
title: '所用时间',
dataIndex: 'cost_time',
align: 'center',
render: (text) => (text && moment(text).format('HH:mm:ss')) || '-',
sorter: (a, b) => a.cost_time - b.cost_time
}
];
useEffect(() => {
changeParams({
page: 1
});
pathId && staticList(pathId);
}, []);
const handleFetchData = () => {
pathId && staticList(pathId);
}
const {
study_count,
course_study_count,
initiative_study,
passed_count,
course_used_count,
school_used_count
} = subject_info;
const maps = {
1: 'subject_info', // 实践课程使用情况
2: 'shixun_info', // 实训使用情况
3: 'user_info' // 用户使用情况
};
const handleTabChange = (key) => {
const type = maps[+key];
// console.log(type);
const params = {
page: 1,
type: type
}
// 恢复初始值
changeParams(params);
initTotal();
pathId && staticList(pathId);
}
return (
<div className="static_wrap">
<div className="ant-spin-container">
<div className="educontent">
<section className="static_section_header">
<div className="header_title">
<span className="title-p">学习统计</span>
<span className="title-sub">Android综合实训之物联网移动应用</span>
</div>
<div className="header-number header-flex">
<StaticNumberAndTxt
count={study_count} // 总数
txt={'学习总人数'} // 文字描述
desc={'学习该课程的全部人数(学习总人数=课堂学习人数+自主学习人数)'}
/>
<StaticNumberAndTxt
count={course_study_count} // 总数
txt={'课堂学习人数'} // 文字描述
desc={'通过课堂学习该课程的人数'}
/>
<StaticNumberAndTxt
count={initiative_study} // 总数
txt={'自主学习人数'} // 文字描述
desc={'通过自主学习该课程的人数'}
/>
<StaticNumberAndTxt
count={passed_count} // 总数
txt={'通关总人数'} // 文字描述
desc={'通关该课程所有实训的人数去重。一个人数计算1次'}
/>
<StaticNumberAndTxt
count={course_used_count} // 总数
txt={'使用课堂数'} // 文字描述
desc={'使用该课程的课堂数量'}
/>
<StaticNumberAndTxt
count={school_used_count} // 总数
txt={'使用单位数'} // 文字描述
desc={'使用该课程的单位数量(包括自主学习者所在单位)'}
/>
</div>
</section>
<section className="static_section_table">
<Tabs defaultActiveKey="1" onChange={handleTabChange} style={{ paddingBottom: '15px'}}>
<TabPane tab="课堂使用情况" key="1">
<DisplayTableData
columns={columns}
datas={other_info}
total={total}
fetchData={handleFetchData}
/>
</TabPane>
<TabPane tab="实际使用情况" key="2">
<DisplayTableData
columns={sxColumns}
datas={other_info}
total={total}
fetchData={handleFetchData}
/>
</TabPane>
<TabPane tab="学习情况" key="3">
<DisplayTableData
columns={stColumns}
datas={other_info}
total={total}
fetchData={handleFetchData}
/>
</TabPane>
</Tabs>
</section>
</div>
</div>
</div>
);
}
const mapStateToProps = (state) => {
const { staticReducer: {subject_info, other_info, total} } = state;
return {
subject_info,
other_info,
total
}
};
const mapDispatchToProps = (dispatch) => ({
staticList: (id) => dispatch(actions.staticList(id)),
changeParams: (params) => dispatch(actions.changeParams(params)),
initTotal: () => dispatch(actions.initTotal())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
// export default App;

@ -0,0 +1,156 @@
.static_wrap {
.static_section_header,
.static_section_table{
background-color: #fff;
border-radius: 5px;
padding: 30px 20px 0;
margin-top: 20px;
}
.static_section_table{
margin-bottom: 140px;
padding-top: 5px;
}
.static_section_header{
.header_title{
line-height: 1;
.title-p,
.title-sub{
display: inline-block;
vertical-align: bottom;
color: #303133;
}
.title-p{
position: relative;
font-size: 20px;
height: 20px;
line-height: 1;
font-weight: bold;
&::before{
position: absolute;
content: '';
border-left: 1px solid rgba(192,196,204,1);
right: -10px;
top: 2px;
bottom: 0px;
margin-left: 10px;
}
}
.title-sub{
margin-left: 20px;
font-size: 16px;
max-width: 1000px;
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;
vertical-align: bottom;
}
}
.header-number{
height: 158px;
}
.header-flex{
display: flex;
justify-content: space-around;
align-items: center;
.static-flex-item{
display: flex;
flex-direction: column;
justify-content: center;
.item-count{
font-size: 24px;
color: #4CACFF;
font-weight: bold;
}
.item-txt{
font-size: 14px;
line-height: 1.5;
text-align: center;
color: #909399;
margin-top: 20px;
.icon{
margin-left: 5px;
font-size: 16px !important;
}
}
}
}
}
// .static_table{
// // .ant-table-header{
// // overflow: hidden !important;
// // margin-bottom: 0px !important;
// // }
// // .ant-table-row-cell-break-word{
// // background: rgba(241,248,255,1) !important;
// // }
// // .overflow_hidden{
// // max-width: 280px;
// // overflow: hidden;
// // text-overflow:ellipsis;
// // white-space: nowrap;
// // }
// }
.static_table{
.ant-table-header{
margin-bottom: 0px !important;
overflow: hidden !important;
}
.ant-table-thead{
th{
background: rgba(241,248,255,1);
}
.ant-table-column-title{
color: #303133;
font-weight: bold;
}
}
.ant-table-tbody tr:nth-child(2n) {
td{
background: rgba(241,248,255,.4);
}
}
.ant-table-tbody tr td{
color: #303133;
}
}
.ant-table-footer{
background-color: rgba(241,248,255,1);
padding: 16px 0px;
}
.footer_list{
display: flex;
// background: #fff;
box-sizing: border-box;
text-align: center;
li{
color: #303133;
}
// border-top: 1px solid green;
.footer_item{
width: 150px;
}
.footer_item:not(:first-child) {
padding-right: 10px;
}
.footer-total{
width: 100px;
}
.footer_name{
flex: 1;
}
}
}
.tool-clazz{
max-width: 200px !important;
}

@ -0,0 +1,99 @@
/*
* @Description: 模拟数据
* @Author: tangjiang
* @Github:
* @Date: 2020-01-11 10:55:33
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-14 09:11:36
*/
import { random } from 'lodash';
const getGuid = () =>
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
/* eslint-disable */
let r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
const fetchData = (startIndex = 0) =>
new Promise(resolve => {
setTimeout(() => {
resolve(
startIndex >= 500 // 总共只有500条数据
? []
: Array.from({ length: 50 }).map((_, i) => {
// 每次返回100条
const index = startIndex + i;
return {
key: getGuid(),
index: `${index}`,
name: '湖南长沙中南大学湖南长沙中南大学湖南长沙中南大学湖南长沙中南大学湖南长沙中南大学',
age: i+1,
address: 'New York No. ',
address2: 'address2',
bbb: 'address3'
};
}),
);
}, random(0, 1.0) * 1000);
});
const columns = [
{
title: '序号',
dataIndex: 'index',
render: text => text + 1,
width: 80,
align: 'center'
},
{
title: '使用单位',
dataIndex: 'name',
width: 300,
className: 'overflow_hidden',
align: 'center'
},
{
title: '使用课堂/个',
width: 200,
dataIndex: 'age',
align: 'center',
// sorter: (a, b) => a.age - b.age
},
{
title: '课堂学生/个',
width: 200,
dataIndex: 'address',
align: 'center',
// sorter: (a, b) => a.age - b.age
},
{
title: '选用实训/个',
width: 200,
dataIndex: 'address2',
align: 'center',
// sorter: (a, b) => a.age - b.age
},
{
title: '选用实训/个',
width: 200,
dataIndex: 'bbb',
align: 'center',
// sorter: (a, b) => a.bbb - b.bbb
}
];
const sumData = [
{
index: '合计',
key: 6,
name: 'Disabled User',
age: 99,
address: 'Sidney No.',
address2: 'address2',
bbb: 'address3',
}
];
export { columns, fetchData, sumData };

@ -88,7 +88,11 @@ const types = {
CHANGE_COMMENT_PAGINATION_PARAMS: 'CHANGE_COMMENT_PAGINATION_PARAMS', // 改变分页
/** tpi */
SHOW_OR_HIDE_TPI_TEST_CASE: 'SHOW_OR_HIDE_TPI_TEST_CASE', // 显示或隐藏tpi测试集弹框
IS_COLLAPSE_TEST_CASE: 'IS_COLLAPSE_TEST_CASE' // 是否展开测试集
IS_COLLAPSE_TEST_CASE: 'IS_COLLAPSE_TEST_CASE', // 是否展开测试集
/** 统计 */
GET_STATIC_INFO: 'GET_STATIC_INFO',
CHANGE_STATIC_PARAMS: 'CHANGE_STATIC_PARAMS',
CHANGE_STATIC_TOTAL: 'CHANGE_STATIC_TOTAL'
}
export default types;

@ -103,6 +103,12 @@ import {
isCollpaseTsetCase
} from './tpi';
import {
staticList,
changeParams,
initTotal
} from './static';
export default {
toggleTodo,
getOJList,
@ -181,5 +187,9 @@ export default {
changePagination,
// tpi
showOrHideTpiTestCase,
isCollpaseTsetCase
isCollpaseTsetCase,
// 统计
staticList,
changeParams,
initTotal
}

@ -0,0 +1,41 @@
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2020-01-14 09:44:02
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-14 17:02:45
*/
import types from "./actionTypes";
import { fetchStaticList } from "../../services/staticService";
export const staticList = (id) => {
return (dispatch, getState) => {
const { params, total_count, other_info } = getState().staticReducer;
if (total_count !== 0 && total_count === other_info.length) return;
fetchStaticList(id, params).then(res => {
// console.log('统计数据=====>>>>>', res);
const {data} = res;
if (data.status === 0) {
dispatch({
type: types.GET_STATIC_INFO,
payload: data.data
});
}
});
}
};
export const changeParams = (params) => {
return {
type: types.CHANGE_STATIC_PARAMS,
payload: params
}
}
export const initTotal = () => {
return {
type: types.CHANGE_STATIC_TOTAL
}
}

@ -16,6 +16,7 @@ import userReducer from './userReducer';
import jupyterReducer from './jupyterReducer';
import commentReducer from './commentReducer';
import tpiReducer from './tpiReducer';
import staticReducer from './staticReducer';
export default combineReducers({
testReducer,
@ -26,5 +27,6 @@ export default combineReducers({
userReducer,
jupyterReducer,
commentReducer,
tpiReducer
tpiReducer,
staticReducer
});

@ -0,0 +1,69 @@
/*
* @Description: 统计
* @Author: tangjiang
* @Github:
* @Date: 2020-01-14 09:34:49
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-14 15:49:55
*/
import types from "../actions/actionTypes";
// const maps = {
// 1: 'shixun_info', // 实训使用情况
// 2: 'user_info', // 用户使用情况
// 3: 'subject_info' // 实践课程使用情况
// }
const initalState = {
subject_info: {},
other_info: [],
total_count: 0,
total: {},
params: {
// sort_by: '',
// sort_direction: 'desc', // desc || asc
limit: 20, // 一页多少条
page: 1, // 第几页
type: 'subject_info' // 类型: 实训 shixun_info,
}
};
// const getGuid = () =>
// 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
// /* eslint-disable */
// let r = (Math.random() * 16) | 0,
// v = c == 'x' ? r : (r & 0x3) | 0x8;
// return v.toString(16);
// });
const staticReducer = (state = initalState, action) => {
const { payload = {}, type } = action;
const {subject_info, other_info = [], total = {}, total_count} = payload;
switch (type) {
case types.GET_STATIC_INFO:
return {
...state,
subject_info,
other_info: state.other_info.concat(other_info),
total,
total_count,
params: Object.assign({}, state.params, { page: state.params.page + 1 })
}
case types.CHANGE_STATIC_PARAMS: {
return {
...state,
params: Object.assign({}, state.params, payload)
};
}
case types.CHANGE_STATIC_TOTAL: {
return {
...state,
other_info: [],
total: {}
}
}
default:
return state;
}
}
export default staticReducer;

@ -0,0 +1,14 @@
import axios from "axios";
/*
* @Description:
* @Author: tangjiang
* @Github:
* @Date: 2020-01-14 09:40:53
* @LastEditors : tangjiang
* @LastEditTime : 2020-01-14 10:47:19
*/
export async function fetchStaticList (id, params) {
const url = `/paths/${id}/statistics_info.json`;
return axios.get(url, { params });
}
Loading…
Cancel
Save