Merge branch 'develop' of http://bdgit.educoder.net/Hjqreturn/educoder into develop
@ -0,0 +1,5 @@
|
|||||||
|
class AddWecharSupportForShixuns < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
add_column :shixuns, :is_wechat_support, :boolean, :default => false
|
||||||
|
end
|
||||||
|
end
|
@ -1,4 +0,0 @@
|
|||||||
class MigrateCourseGroupMemberCount < ActiveRecord::Migration[5.2]
|
|
||||||
def change
|
|
||||||
end
|
|
||||||
end
|
|
@ -0,0 +1,8 @@
|
|||||||
|
class ModifyWechatSupportForShixuns < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
name = ["Python基础", "C语言基础", "C++基础", "Java"]
|
||||||
|
shixuns = Shixun.includes(:tag_repertoires)
|
||||||
|
.where(tag_repertoires: {name: name}, shixuns: {status: 2, hide_code: false})
|
||||||
|
shixuns.update_all(is_wechat_support: true)
|
||||||
|
end
|
||||||
|
end
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 154 KiB |
After Width: | Height: | Size: 451 B |
After Width: | Height: | Size: 13 KiB |
@ -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,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 };
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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 });
|
||||||
|
}
|