├── public
├── favicon.ico
├── imgs
│ ├── logo.png
│ └── logo-big.png
├── livePlayer.js
├── index.html
└── nep.min.css
├── src
├── App.test.js
├── actions
│ ├── verifyResource.action.js
│ ├── auditResource.action.js
│ ├── login.action.js
│ ├── livePlayer.js
│ ├── livePlayer.action.js
│ ├── exam.action.js
│ ├── live.action.js
│ ├── course.action.js
│ ├── teacher.action.js
│ ├── school.action.js
│ └── knowledge.ation.js
├── reducers
│ ├── livePlayer.reducer.js
│ ├── login.reducer.js
│ ├── school.reducer.js
│ ├── index.js
│ ├── knowledgeTree.reducer.js
│ ├── exam.reducer.js
│ ├── live.reducer.js
│ ├── course.reducer.js
│ └── teacher.reducer.js
├── index.css
├── components
│ ├── SearchBox.js
│ ├── InfiniteScrollCtrl.js
│ ├── ExerciseConent.js
│ ├── knowledgeTreeItem.js
│ ├── knowledgeTreeLine.js
│ ├── filterHeader.js
│ └── tree.js
├── config.js
├── utils
│ ├── ajaxMethod.js
│ ├── constants.js
│ ├── util.js
│ ├── httpReqApi.js
│ ├── TreeToo.js
│ └── tableColumnsDef.js
├── App.js
├── index.js
├── containers
│ ├── login.js
│ ├── school.js
│ ├── teacher.js
│ ├── exam.js
│ ├── live.js
│ ├── course.js
│ ├── knowledgeTree.js
│ ├── schoolDetail.js
│ ├── tearcherDetail.js
│ ├── exerciseDetail.js
│ ├── liveDetail.js
│ └── courseDetail.js
└── App.css
├── .gitignore
└── package.json
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeDebugTest/react/master/public/favicon.ico
--------------------------------------------------------------------------------
/public/imgs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeDebugTest/react/master/public/imgs/logo.png
--------------------------------------------------------------------------------
/public/imgs/logo-big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codeDebugTest/react/master/public/imgs/logo-big.png
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/public/livePlayer.js:
--------------------------------------------------------------------------------
1 | function livePlayer(divId, initOptions, initCallback) {
2 | const pullStreamObj = new neplayer(divId, initOptions,
3 | () => {
4 | initCallback(pullStreamObj);
5 | }
6 | );
7 | }
8 |
9 | window.livePlayer = livePlayer;
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 | .idea
19 |
20 |
--------------------------------------------------------------------------------
/src/actions/verifyResource.action.js:
--------------------------------------------------------------------------------
1 | import {verifyResource} from '../utils/httpReqApi'
2 |
3 | export function doVerifyResource(data, successFunc, failedFuc) {
4 | verifyResource(data).then(
5 | response => {
6 | if (response.code === undefined) {
7 | alert(`审批失败:未收到请求结果!`);
8 | return;
9 | }
10 |
11 | if(response.code === 0) {
12 | successFunc();
13 | } else {
14 | failedFuc(response.message);
15 | }
16 | }
17 | );
18 | }
--------------------------------------------------------------------------------
/src/reducers/livePlayer.reducer.js:
--------------------------------------------------------------------------------
1 | import {CREATE_LIVE_PLAYER, RELEASE_LIVE_PLAYER} from '../actions/livePlayer.action'
2 |
3 |
4 | export function livePlayerReducer(state = {}, action){
5 | switch (action.type) {
6 | case CREATE_LIVE_PLAYER:
7 | return Object.assign({}, state, {livePlayer: action.livePlayer});
8 | case RELEASE_LIVE_PLAYER:
9 | if (state.livePlayer)
10 | state.livePlayer.release();
11 | return Object.assign({}, state, {livePlayer: null});
12 | default:
13 | return state;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 | .login-page {
7 | background-color: #0c6561;
8 | height: 100%;
9 | width: 100%;
10 | position: absolute;
11 | }
12 | .login-wrap {
13 | position:absolute;
14 | top:0;
15 | left: 0;
16 | right:0;
17 | bottom:0;
18 | margin:auto;
19 | width:300px;
20 | height:350px;
21 | }
22 |
23 | .login-wrap .login-form {
24 | max-width: 300px;
25 | }
26 | .login-wrap .login-form-button {
27 | width: 100%;
28 | }
29 | .login-img{
30 | height: 75px;
31 | width: 250px;
32 | margin-left: 30px;
33 | }
34 |
35 | .radio-style {
36 | display: inline;
37 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yixi-back-management",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "antd": "^2.10.0",
7 | "babel-polyfill": "^6.23.0",
8 | "react": "^15.5.4",
9 | "react-dom": "^15.5.4",
10 | "react-redux": "^5.0.4",
11 | "react-router": "^3.0.2",
12 | "redux": "^3.6.0",
13 | "redux-thunk": "^2.2.0"
14 | },
15 | "devDependencies": {
16 | "babel-plugin-import": "^1.1.0",
17 | "react-scripts": "0.9.5"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test --env=jsdom",
23 | "eject": "react-scripts eject"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/reducers/login.reducer.js:
--------------------------------------------------------------------------------
1 | import {LOGIN, LOGIN_SUCCESS, LOGIN_FAILED, } from '../actions/login.action'
2 |
3 | export function loginReducer(state = {}, action){
4 | switch (action.type) {
5 | case LOGIN:
6 | return Object.assign({}, state, {username: action.info.username, isFetchingLogin: true});
7 | case LOGIN_SUCCESS:
8 | // action.successFunc();
9 | return Object.assign({}, state, {userInfo: action.response.content, userHasLogin: true });
10 | case LOGIN_FAILED:
11 | // action.failedFuc(action.response.message);
12 | return Object.assign({}, state, {message: action.response.message, userHasLogin: false });
13 | default:
14 | return state;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/reducers/school.reducer.js:
--------------------------------------------------------------------------------
1 | import {FETCH_SCHOOL_LIST, FETCH_SCHOOL_SUCCESS, FETCH_SCHOOL_FAILED, SHOW_SCHOOL_DETAIL} from '../actions/school.action'
2 |
3 | export function schoolReducer(state = {schoolList: []}, action){
4 | switch (action.type) {
5 | case FETCH_SCHOOL_LIST:
6 | return Object.assign({}, state, {loading: true, schoolList: []});
7 | case FETCH_SCHOOL_SUCCESS:
8 | return Object.assign({}, state, {loading: false, schoolList: action.response.content});
9 | case FETCH_SCHOOL_FAILED:
10 | return Object.assign({}, state, {loading: false, schoolList: []});
11 | case SHOW_SCHOOL_DETAIL:
12 | return Object.assign({}, state, {school: action.school});
13 | default:
14 | return state;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from 'redux'
2 | import {loginReducer} from './login.reducer'
3 | import {knowledgeTreeReducer} from './knowledgeTree.reducer'
4 | import {courseReducer} from './course.reducer'
5 | import {examReducer} from './exam.reducer'
6 | import {teacherReducer} from './teacher.reducer'
7 | import {liveReducer} from './live.reducer'
8 | import {schoolReducer} from './school.reducer'
9 | import {livePlayerReducer} from './livePlayer.reducer'
10 |
11 | const appReducer = combineReducers({
12 | login: loginReducer,
13 | dictionary: knowledgeTreeReducer,
14 | course: courseReducer,
15 | exam: examReducer,
16 | teacher: teacherReducer,
17 | live: liveReducer,
18 | school: schoolReducer,
19 | livePlayer: livePlayerReducer
20 | });
21 |
22 | export default appReducer
--------------------------------------------------------------------------------
/src/components/SearchBox.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {Input} from 'antd'
3 | import '../App.css'
4 |
5 | const Search = Input.Search;
6 |
7 | class SearchBox extends Component {
8 | constructor(props) {
9 | super(props);
10 | }
11 |
12 | onSearchSelect(value) {
13 | console.log(`search text: ${value}`);
14 | this.props.searchFunc(value);
15 | }
16 |
17 | render() {
18 | return (
19 |
20 |
21 |
25 |
26 | )
27 | }
28 | }
29 |
30 | export default SearchBox;
--------------------------------------------------------------------------------
/src/reducers/knowledgeTree.reducer.js:
--------------------------------------------------------------------------------
1 | import {FETCH_DICTIONARY, FETCH_DICTIONARY_SUCCESS, FETCH_DICTIONARY_FAILED,
2 | FETCH_KNOWLEDGE_TREE_SUCCESS} from '../actions/knowledge.ation'
3 |
4 | export function knowledgeTreeReducer(state = {}, action){
5 | switch (action.type) {
6 | case FETCH_DICTIONARY:
7 | return Object.assign({}, state, {loading: true});
8 | case FETCH_DICTIONARY_SUCCESS:
9 | return Object.assign({}, state, {loading: false, dictionary: action.response.content});
10 | case FETCH_KNOWLEDGE_TREE_SUCCESS:
11 | return Object.assign({}, state, {loading: false, knowledgeTree: action.response.content});
12 | case FETCH_DICTIONARY_FAILED:
13 | return Object.assign({}, state, {loading: false});
14 | default:
15 | return state;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 |
2 | const config = {
3 | fakeHost: 'http://localhost:8080',
4 | realHost: 'http://120.27.233.176:5051',
5 | testHost: 'http://192.168.1.137:5051',
6 | imgUrlDir: "http://yixi-1.oss-cn-hangzhou.aliyuncs.com/"
7 | };
8 |
9 | const userList = {
10 | 'liuye': true,
11 | 'sunjian11': true,
12 | 'tony': true,
13 | 'sunjian': true,
14 | 'zhanzushun': true,
15 | 'chenchunbin': true,
16 | 'zhangpengfei': true,
17 | 'liwenshuai': true,
18 | 'test': true,
19 | 'shezhidian': true,
20 | 'xielei': true,
21 | 'wangzhiwei': true,
22 | 'wanglin': true,
23 | 'CC': true,
24 | 'binson': true,
25 | 'yuqikai': true,
26 | 'sky': true,
27 | '陈霆': true,
28 | '杜力耘': true,
29 | '田万国': true,
30 | '金老师': true,
31 | '李春阳': true,
32 | '薛小智': true,
33 | '郑老师': true,
34 | '张老师': true,
35 | '詹祖顺': true,
36 | }
37 |
38 | export {config, userList}
39 |
--------------------------------------------------------------------------------
/src/reducers/exam.reducer.js:
--------------------------------------------------------------------------------
1 | import {FETCH_EXAM_LIST,FETCH_EXAM_SUCCESS, FETCH_EXAM_FAILED,
2 | DELETE_EXAM_SUCCESS, SHOW_EXAM_DETAIL} from '../actions/exam.action'
3 |
4 | export function examReducer(state = {}, action){
5 | switch (action.type) {
6 | case FETCH_EXAM_LIST:
7 | return Object.assign({}, state, {loading: true, examList: []});
8 | case FETCH_EXAM_SUCCESS:
9 | return Object.assign({}, state, {loading: false, examList: action.response.content});
10 | case FETCH_EXAM_FAILED:
11 | return Object.assign({}, state, {loading: false, examList: []});
12 | case SHOW_EXAM_DETAIL:
13 | return Object.assign({}, state, {exercise: action.exercise});
14 | case DELETE_EXAM_SUCCESS:
15 | state.examList.splice(action.index, 1);
16 | return Object.assign({}, state);
17 | default:
18 | return state;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/reducers/live.reducer.js:
--------------------------------------------------------------------------------
1 | import {FETCH_LIVE_LIST, FETCH_LIVE_SUCCESS, FETCH_LIVE_FAILED,
2 | DELETE_LIVE_SUCCESS, DELETE_LIVE_FAILED, SHOW_LIVE_DETAIL} from '../actions/live.action'
3 |
4 | export function liveReducer(state = {}, action){
5 | switch (action.type) {
6 | case FETCH_LIVE_LIST:
7 | return Object.assign({}, state, {loading: true, liveList: []});
8 | case FETCH_LIVE_SUCCESS:
9 | return Object.assign({}, state, {loading: false, liveList: action.response.content});
10 | case FETCH_LIVE_FAILED:
11 | return Object.assign({}, state, {loading: false, liveList: []});
12 | case SHOW_LIVE_DETAIL:
13 | return Object.assign({}, state, {live: action.live});
14 | case DELETE_LIVE_SUCCESS:
15 | state.liveList.splice(action.index, 1);
16 | return Object.assign({}, state);
17 | default:
18 | return state;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/reducers/course.reducer.js:
--------------------------------------------------------------------------------
1 | import {FETCH_COURSE_LIST, FETCH_COURSE_SUCCESS, FETCH_COURSE_FAILED,
2 | DELETE_COURSE_SUCCESS, SHOW_COURSE_DETAIL} from '../actions/course.action'
3 |
4 | export function courseReducer(state = {}, action){
5 | switch (action.type) {
6 | case FETCH_COURSE_LIST:
7 | return Object.assign({}, state, {loading: true, courseList: []});
8 | case FETCH_COURSE_SUCCESS:
9 | return Object.assign({}, state, {loading: false, courseList: action.response.content});
10 | case FETCH_COURSE_FAILED:
11 | return Object.assign({}, state, {loading: false, courseList: []});
12 | case SHOW_COURSE_DETAIL:
13 | return Object.assign({}, state, {course: action.course});
14 | case DELETE_COURSE_SUCCESS:
15 | state.courseList.splice(action.index, 1);
16 | return Object.assign({}, state);
17 | default:
18 | return state;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/reducers/teacher.reducer.js:
--------------------------------------------------------------------------------
1 | import {FETCH_TEACHER_LIST, FETCH_TEACHER_SUCCESS, FETCH_TEACHER_FAILED,
2 | DELETE_TEACHER_SUCCESS, DELETE_TEACHER_FAILED, SHOW_TEACHER_DETAIL} from '../actions/teacher.action'
3 |
4 | export function teacherReducer(state = {}, action){
5 | switch (action.type) {
6 | case FETCH_TEACHER_LIST:
7 | return Object.assign({}, state, {loading: true, teacherList: []});
8 | case FETCH_TEACHER_SUCCESS:
9 | return Object.assign({}, state, {loading: false, teacherList: action.response.content});
10 | case FETCH_TEACHER_FAILED:
11 | return Object.assign({}, state, {loading: false, teacherList: []});
12 | case SHOW_TEACHER_DETAIL:
13 | return Object.assign({}, state, {teacher: action.teacher});
14 | case DELETE_TEACHER_SUCCESS:
15 | state.teacherList.splice(action.index, 1);
16 | return Object.assign({}, state);
17 | default:
18 | return state;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/InfiniteScrollCtrl.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { BackTop } from 'antd';
3 | import InfiniteScroll from 'react-infinite-scroller';
4 |
5 | export default class InfiniteScrollCtrl extends Component {
6 | constructor(props, context) {
7 | super(props, context);
8 | }
9 | render() {
10 | return (
11 |
12 | Loading ...
}
17 | useWindow={true}
18 | threshold={10}
19 | initialLoad={false}
20 | >
21 | {
22 | this.props.items
23 | }
24 |
25 |
26 |
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/actions/auditResource.action.js:
--------------------------------------------------------------------------------
1 | import {auditResource, udpateResourceKnowledgeTree} from '../utils/httpReqApi'
2 |
3 | export function doAuditResource(data, successFunc, failedFuc) {
4 | auditResource(data).then(
5 | response => {
6 | if (!response || response.code === undefined) {
7 | alert(`审批失败:未收到请求结果!`);
8 | return;
9 | }
10 |
11 | if(response.code === 0) {
12 | successFunc();
13 | } else {
14 | failedFuc(response.message);
15 | }
16 | }
17 | );
18 | }
19 |
20 | export function doUpdateKnowledgeTree(data, successFunc, failedFunc) {
21 | return udpateResourceKnowledgeTree(data).then(
22 | response => {
23 | if (!response || response.code === undefined) {
24 | alert(`更新资源知识树失败!`);
25 | return;
26 | }
27 |
28 | if(response.code === 0) {
29 | successFunc();
30 | } else {
31 | failedFunc(response.message);
32 | }
33 | }
34 | )
35 | }
--------------------------------------------------------------------------------
/src/utils/ajaxMethod.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch'
2 |
3 | const ajaxPost = (url, header, params, callback) => {
4 | const headers = new Headers({
5 | 'Accept': 'application/json',
6 | 'Content-Type': 'application/json',
7 | ...header
8 | });
9 | console.log('------>>>> ' + url);
10 |
11 | const requestBody = JSON.stringify(params);
12 | console.log(' ------ body ' + requestBody);
13 | return fetch(url, {
14 | method: 'POST',
15 | headers: headers,
16 | body: requestBody
17 | }).then(
18 | response => {return response.json() }
19 | ).then(
20 | json => {
21 | console.dir(json);
22 | console.log('<<<<----- ');
23 | return json
24 | }
25 | ).catch(
26 | error => console.log(error)
27 | );
28 | };
29 |
30 | const ajaxGet = (url, headersObject, callback) => {
31 | return fetch(url, {
32 | method: 'GET',
33 | headers: {
34 | 'Accept': 'application/json',
35 | ...headersObject
36 | }
37 | }).then(
38 | response => {return response.json()}
39 | ).then(
40 | json => {callback(json)}
41 | ).catch(
42 | error => console.log(error)
43 | );
44 | };
45 |
46 | export {ajaxGet, ajaxPost};
47 |
--------------------------------------------------------------------------------
/src/actions/login.action.js:
--------------------------------------------------------------------------------
1 | import {userLogin} from '../utils/httpReqApi'
2 | export const LOGIN = 'login';
3 | export const LOGIN_SUCCESS= 'login_success';
4 | export const LOGIN_FAILED = 'login_failed';
5 |
6 | const login = (obj) => {
7 | return {
8 | type: LOGIN,
9 | info: obj
10 | }
11 | };
12 |
13 | const loginSuccess = (response) => {
14 | return {
15 | type: LOGIN_SUCCESS,
16 | response: response,
17 | }
18 | };
19 |
20 | const loginFailed = (response) => {
21 | return {
22 | type: LOGIN_FAILED,
23 | response: response,
24 | }
25 | };
26 |
27 | export function fetchLogin(data, successFunc, failedFuc) {
28 | return dispatch => {
29 | dispatch(login(data));
30 |
31 | return userLogin(data).then(
32 | response => {
33 | if (!response || response.code === undefined) {
34 | alert(`No code in result of post: login`);
35 | return;
36 | }
37 |
38 | if(response.code === 0 && response.content) {
39 | dispatch(loginSuccess(response));
40 | successFunc();
41 | } else {
42 | dispatch(loginFailed(response));
43 | failedFuc(response.message);
44 | }
45 | }
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
17 | React App
18 |
19 |
20 |
21 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/actions/livePlayer.js:
--------------------------------------------------------------------------------
1 |
2 | export const CREATE_LIVE_PLAYER = 'create_live_player';
3 |
4 | const createLiveAction = (data) => {
5 | return {
6 | type: CREATE_LIVE_PLAYER,
7 | livePlayer: data
8 | }
9 | };
10 |
11 | const createLivePlayer = function (liveConfig, dispatch) {
12 | window.livePlayer(
13 | liveConfig.liveBox,
14 | {
15 | "controls":true,
16 | "autoplay":false,
17 | "preload":"metadata",
18 | "techOrder": ["html5", "flash"],
19 | "loop":false,
20 | "bigPlayButton":true,
21 | "streamTimeoutTime": 10 * 1000,
22 | "controlBar":{
23 | "playToggle":true,
24 | "volumeMenuButton":true,
25 | "currentTimeDisplay":true,
26 | "timeDivider":true,
27 | "durationDisplay":true,
28 | "progressControl":true,
29 | "liveDisplay":true,
30 | "remainingTimeDisplay":false,
31 | "fullscreenToggle":true,
32 | }
33 | },
34 | livePlayer => {
35 | livePlayer.setDataSource({
36 | type: liveConfig.playerType,
37 | src: liveConfig.pullStreamUrl
38 | });
39 |
40 | dispatch(createLiveAction(livePlayer))
41 | }
42 | );
43 |
44 | };
45 |
46 | export function doCreateLivePlayer(liveConfig) {
47 | return dispatch => {
48 | createLivePlayer(liveConfig, dispatch)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/utils/constants.js:
--------------------------------------------------------------------------------
1 | const SINGLE_SELECT_EXERCISE = {value: 1, name: '单选题'};
2 | const FILL_EXERCISE = {value: 2, name: '填空题'};
3 | const MULTI_SELECT_EXERCISE = {value: 3, name: '多选题'};
4 | const JUDGE_EXERCISE = {value: 4, name: '判断题'};
5 | const SUBJECT_EXERCISE = {value: 5, name: '主观题'};
6 |
7 | const EXERCISE_TYPE = {};
8 | EXERCISE_TYPE[SINGLE_SELECT_EXERCISE.value] = SINGLE_SELECT_EXERCISE;
9 | EXERCISE_TYPE[FILL_EXERCISE.value] = FILL_EXERCISE;
10 | EXERCISE_TYPE[MULTI_SELECT_EXERCISE.value] = MULTI_SELECT_EXERCISE;
11 | EXERCISE_TYPE[JUDGE_EXERCISE.value] = JUDGE_EXERCISE;
12 | EXERCISE_TYPE[SUBJECT_EXERCISE.value] = SUBJECT_EXERCISE;
13 |
14 |
15 | const TABLE_PAGE_SIZE = 20;
16 |
17 | const Biz_Target_Status = {
18 | UN_SUBMIT: 1, // 未上架
19 | SUBMITTED: 2, //上架审核中
20 | RELEASED: 3, //已上架发布
21 | REMOVED: 4, //已下架
22 | UN_PASSED: 5, //未通过
23 | };
24 |
25 | const Biz_Target_Type = {
26 | TEACHER: 1, // 教师
27 | COURSE: 2, //课程
28 | EXERCISE: 3, //习题
29 | ALBUM: 4, //专辑
30 | EXAM: 5, //试卷
31 | CLASS: 6, //班级
32 | LIVE: 7, //直播
33 | STUDENT: 8, //学生
34 | SCHOOL: 9 //学校
35 | }
36 |
37 | const CourseItemType = {
38 | VIDEO: 1, //视频
39 | COURSE_CASE: 2, //课件
40 | LEARNING_CASE: 3 //学案
41 | }
42 |
43 | const GENDER_MALE = '1'; //男
44 | const GENDER_FEMALE = '2'; //女
45 |
46 | export {EXERCISE_TYPE, TABLE_PAGE_SIZE, Biz_Target_Status, Biz_Target_Type, SINGLE_SELECT_EXERCISE,
47 | FILL_EXERCISE, JUDGE_EXERCISE, MULTI_SELECT_EXERCISE, SUBJECT_EXERCISE, CourseItemType, GENDER_MALE, GENDER_FEMALE}
--------------------------------------------------------------------------------
/src/actions/livePlayer.action.js:
--------------------------------------------------------------------------------
1 |
2 | export const CREATE_LIVE_PLAYER = 'create_live_player';
3 | export const RELEASE_LIVE_PLAYER = 'release_live_player';
4 |
5 | const createAction = (data) => {
6 | return {
7 | type: CREATE_LIVE_PLAYER,
8 | livePlayer: data
9 | }
10 | };
11 |
12 | const releaseAction = () => {
13 | return {
14 | type: RELEASE_LIVE_PLAYER,
15 | }
16 | };
17 |
18 | const createLivePlayer = function (liveConfig, dispatch) {
19 | window.livePlayer(
20 | liveConfig.liveBox,
21 | {
22 | "controls":true,
23 | "autoplay":false,
24 | "preload":"metadata",
25 | "techOrder": ["html5", "flash"],
26 | "loop":false,
27 | "bigPlayButton":true,
28 | "streamTimeoutTime": 10 * 1000,
29 | "controlBar":{
30 | "playToggle":true,
31 | "volumeMenuButton":true,
32 | "currentTimeDisplay":true,
33 | "timeDivider":true,
34 | "durationDisplay":true,
35 | "progressControl":true,
36 | "liveDisplay":true,
37 | "remainingTimeDisplay":false,
38 | "fullscreenToggle":true,
39 | }
40 | },
41 | livePlayer => {
42 | livePlayer.setDataSource({
43 | type: liveConfig.playerType,
44 | src: liveConfig.pullStreamUrl
45 | });
46 |
47 | dispatch(createAction(livePlayer))
48 | }
49 | );
50 |
51 | };
52 |
53 | export function doCreateLivePlayer(liveConfig) {
54 | return dispatch => {
55 | createLivePlayer(liveConfig, dispatch)
56 | }
57 | }
58 |
59 | export function doReleaseLivePlayer() {
60 | return dispatch => {
61 | dispatch(releaseAction())
62 | }
63 | }
--------------------------------------------------------------------------------
/src/actions/exam.action.js:
--------------------------------------------------------------------------------
1 | import {fetchExamList} from '../utils/httpReqApi'
2 |
3 | export const FETCH_EXAM_LIST = 'fetch_exam_list';
4 | export const FETCH_EXAM_SUCCESS = 'fetch_exam_success';
5 | export const FETCH_EXAM_FAILED = 'fetch_exam_failed';
6 | export const DELETE_EXAM_SUCCESS = 'delete_exam_success';
7 | export const DELETE_EXAM_FAILED = 'delete_exam_failed';
8 | export const SHOW_EXAM_DETAIL = 'show_exam_detail';
9 |
10 | const doFetch = () => {
11 | return {
12 | type: FETCH_EXAM_LIST,
13 | }
14 | };
15 | const doSuccess = (response) => {
16 | return {
17 | type: FETCH_EXAM_SUCCESS,
18 | response: response
19 | }
20 | };
21 |
22 | const doFailed = (response) => {
23 | return {
24 | type: FETCH_EXAM_FAILED,
25 | response: response
26 | }
27 | };
28 |
29 | const deleteSuccess = (index) => {
30 | return {
31 | type: DELETE_EXAM_SUCCESS,
32 | index: index
33 | }
34 | };
35 |
36 | const showDetail = (exercise) => {
37 | return {
38 | type: SHOW_EXAM_DETAIL,
39 | exercise: exercise
40 | }
41 | };
42 |
43 | export function doFetchExamList(data, successFunc, failedFuc) {
44 | return dispatch => {
45 | dispatch(doFetch(data));
46 |
47 | return fetchExamList(data).then(
48 | response => {
49 | if (response.code === undefined) {
50 | alert(`No code in result of post: fetch course list`);
51 | return;
52 | }
53 |
54 | if(response.code === 0 && response.content) {
55 | dispatch(doSuccess(response));
56 | } else {
57 | dispatch(doFailed(response));
58 | failedFuc(response.message);
59 | }
60 | }
61 | );
62 | }
63 | }
64 |
65 | export function doDeleteExam(data, successFunc, failedFunc) {
66 | return dispatch => {
67 | dispatch(deleteSuccess(data));
68 | successFunc();
69 | }
70 | }
71 |
72 | export function doShowDetail(data) {
73 | return dispatch => {
74 | dispatch(showDetail(data))
75 | }
76 | }
--------------------------------------------------------------------------------
/src/utils/util.js:
--------------------------------------------------------------------------------
1 | import {Biz_Target_Status} from '../utils/constants'
2 |
3 | const isArray = (arg) => {
4 | // support ie 8
5 | if (!Array.isArray) {
6 | Array.isArray = (arg) => {
7 | return typeof arg === 'object' && Object.prototype.toString.call(arg).slice(8, -1) === 'Array';
8 | }
9 | }
10 | return Array.isArray(arg);
11 | };
12 |
13 | const isObject = (value) => {
14 | return value !== null && typeof value === 'object' && Object.prototype.toString.call(value).slice(8, -1) === 'Object';
15 | };
16 |
17 | const varNotEmpty = (value) => {
18 | try {
19 | if (typeof value === 'undefined')
20 | return false;
21 | if (value === null)
22 | return false;
23 | if (typeof value === 'string' && value === '')
24 | return false;
25 | if (typeof value === 'object' && Object.keys(value).length === 0)
26 | return false;
27 | return true;
28 | }
29 | catch (e) {
30 | return false;
31 | }
32 | };
33 |
34 | const varEmpty = (value) => {
35 | return !varNotEmpty(value);
36 | };
37 |
38 | const keyNotEmpty = (obj, key) => {
39 | if (varEmpty(obj))
40 | return false;
41 | return varNotEmpty(obj[key]);
42 | };
43 |
44 | const keyEmpty = (obj, key) => {
45 | return !varNotEmpty(obj[key]);
46 | };
47 |
48 | const mapTagIdsToNames = (tagList, tagIds) => {
49 | let tagNames = [];
50 | if (varNotEmpty(tagIds) && varNotEmpty(tagList)) {
51 | tagIds.map(tagId => {
52 | let matched = tagList.filter(tag => tag.id.toString() === tagId);
53 | if (varNotEmpty(matched))
54 | tagNames.push(matched[0].display);
55 | });
56 | }
57 | return tagNames.toString();
58 | };
59 |
60 | const getOrDefault = (var1, def) => {
61 | return varNotEmpty(var1) ? var1 : def;
62 | }
63 |
64 | const strFindIgnoreCase = (str, subStr) => {
65 | return str.search(new RegExp(subStr, "i"));
66 | };
67 |
68 | const isVerified = (status) => {
69 | return status === Biz_Target_Status.UN_PASSED || status === Biz_Target_Status.RELEASED
70 | };
71 |
72 | export {isArray, isObject, varEmpty, keyNotEmpty, varNotEmpty, keyEmpty, mapTagIdsToNames, getOrDefault, isVerified,
73 | strFindIgnoreCase}
--------------------------------------------------------------------------------
/src/actions/live.action.js:
--------------------------------------------------------------------------------
1 | import {fetchLiveList} from '../utils/httpReqApi'
2 |
3 | export const FETCH_LIVE_LIST = 'fetch_live_list';
4 | export const FETCH_LIVE_SUCCESS = 'fetch_live_success';
5 | export const FETCH_LIVE_FAILED = 'fetch_live_failed';
6 | export const DELETE_LIVE_SUCCESS = 'delete_live_success';
7 | export const DELETE_LIVE_FAILED = 'delete_live_failed';
8 | export const SHOW_LIVE_DETAIL = 'show_live_detail';
9 |
10 | const doFetch = () => {
11 | return {
12 | type: FETCH_LIVE_LIST,
13 | }
14 | };
15 | const doSuccess = (response) => {
16 | return {
17 | type: FETCH_LIVE_SUCCESS,
18 | response: response
19 | }
20 | };
21 |
22 | const doFailed = (response) => {
23 | return {
24 | type: FETCH_LIVE_FAILED,
25 | response: response
26 | }
27 | };
28 |
29 | const deleteSuccess = (index) => {
30 | return {
31 | type: DELETE_LIVE_SUCCESS,
32 | index: index
33 | }
34 | }
35 |
36 | const deleteFailed = (response) => {
37 | return {
38 | type: DELETE_LIVE_FAILED,
39 | index: response
40 | }
41 | }
42 |
43 | const showDetail = (live) => {
44 | return {
45 | type: SHOW_LIVE_DETAIL,
46 | live: live
47 | }
48 | };
49 |
50 |
51 | export function doFetchLiveList(data, successFunc, failedFuc) {
52 | return dispatch => {
53 | dispatch(doFetch(data));
54 |
55 | return fetchLiveList(data).then(
56 | response => {
57 | if (!response || response.code === undefined) {
58 | alert(`No code in result of post: fetch course list`);
59 | return;
60 | }
61 |
62 | if(response.code === 0 && response.content) {
63 | dispatch(doSuccess(response));
64 | } else {
65 | dispatch(doFailed(response));
66 | failedFuc(response.message);
67 | }
68 | }
69 | );
70 | }
71 | }
72 |
73 | export function doDeleteLive(data, successFunc, failedFunc) {
74 | return dispatch => {
75 | dispatch(deleteSuccess(data));
76 | successFunc();
77 | }
78 | }
79 |
80 | export function doShowDetail(data) {
81 | return dispatch => {
82 | dispatch(showDetail(data))
83 | }
84 | }
--------------------------------------------------------------------------------
/src/actions/course.action.js:
--------------------------------------------------------------------------------
1 | import {fetchCourseList} from '../utils/httpReqApi'
2 |
3 | export const FETCH_COURSE_LIST = 'fetch_course_list';
4 | export const FETCH_COURSE_SUCCESS = 'fetch_course_success';
5 | export const FETCH_COURSE_FAILED = 'fetch_course_failed';
6 | export const DELETE_COURSE_SUCCESS = 'delete_course_success';
7 | export const DELETE_COURSE_FAILED = 'delete_course_failed';
8 | export const SHOW_COURSE_DETAIL = 'show_course_detail';
9 |
10 | const doFetch = () => {
11 | return {
12 | type: FETCH_COURSE_LIST,
13 | }
14 | };
15 | const doSuccess = (response) => {
16 | return {
17 | type: FETCH_COURSE_SUCCESS,
18 | response: response
19 | }
20 | };
21 |
22 | const doFailed = (response) => {
23 | return {
24 | type: FETCH_COURSE_FAILED,
25 | response: response
26 | }
27 | };
28 |
29 | const deleteSuccess = (index) => {
30 | return {
31 | type: DELETE_COURSE_SUCCESS,
32 | index: index
33 | }
34 | };
35 |
36 | const showDetail = (course) => {
37 | return {
38 | type: SHOW_COURSE_DETAIL,
39 | course: course
40 | }
41 | };
42 |
43 | const deleteFailed = (response) => {
44 | return {
45 | type: DELETE_COURSE_FAILED,
46 | index: response
47 | }
48 | }
49 |
50 | export function doFetchCourseList(data, successFunc, failedFunc) {
51 | return dispatch => {
52 | dispatch(doFetch(data));
53 |
54 | return fetchCourseList(data).then(
55 | response => {
56 | if (response.code === undefined) {
57 | alert(`No code in result of post: fetch course list`);
58 | return;
59 | }
60 |
61 | if(response.code === 0 && response.content) {
62 | dispatch(doSuccess(response));
63 | } else {
64 | dispatch(doFailed(response));
65 | failedFunc(response.message);
66 | }
67 | }
68 | );
69 | }
70 | }
71 |
72 | export function doDeleteCourse(data, successFunc, failedFunc) {
73 | return dispatch => {
74 | dispatch(deleteSuccess(data));
75 | successFunc();
76 | }
77 | }
78 |
79 | export function doShowDetail(data) {
80 | return dispatch => {
81 | dispatch(showDetail(data))
82 | }
83 | }
--------------------------------------------------------------------------------
/src/actions/teacher.action.js:
--------------------------------------------------------------------------------
1 | import {fetchTeacherList} from '../utils/httpReqApi'
2 |
3 | export const FETCH_TEACHER_LIST = 'fetch_teacher_list';
4 | export const FETCH_TEACHER_SUCCESS = 'fetch_teacher_success';
5 | export const FETCH_TEACHER_FAILED = 'fetch_teacher_failed';
6 | export const DELETE_TEACHER_SUCCESS = 'delete_teacher_success';
7 | export const DELETE_TEACHER_FAILED = 'delete_teacher_failed';
8 | export const SHOW_TEACHER_DETAIL = 'show_teacher_detail';
9 |
10 | const doFetch = () => {
11 | return {
12 | type: FETCH_TEACHER_LIST,
13 | }
14 | };
15 | const doSuccess = (response) => {
16 | return {
17 | type: FETCH_TEACHER_SUCCESS,
18 | response: response
19 | }
20 | };
21 |
22 | const doFailed = (response) => {
23 | return {
24 | type: FETCH_TEACHER_SUCCESS,
25 | response: response
26 | }
27 | };
28 |
29 | const deleteSuccess = (index) => {
30 | return {
31 | type: DELETE_TEACHER_SUCCESS,
32 | index: index
33 | }
34 | }
35 |
36 | const deleteFailed = (response) => {
37 | return {
38 | type: DELETE_TEACHER_FAILED,
39 | index: response
40 | }
41 | }
42 |
43 | const showDetail = (teacher) => {
44 | return {
45 | type: SHOW_TEACHER_DETAIL,
46 | teacher: teacher
47 | }
48 | };
49 |
50 | export function doFetchTeacherList(data, successFunc, failedFuc) {
51 | return dispatch => {
52 | dispatch(doFetch(data));
53 |
54 | return fetchTeacherList(data).then(
55 | response => {
56 | if (!response || response.code === undefined) {
57 | alert(`获取课程列表失败`);
58 | return;
59 | }
60 |
61 | if(response.code === 0 && response.content) {
62 | dispatch(doSuccess(response));
63 | } else {
64 | dispatch(doFailed(response));
65 | failedFuc(response.message);
66 | }
67 | }
68 | );
69 | }
70 | }
71 |
72 | export function doDeleteTeacher(data, successFunc, failedFunc) {
73 | return dispatch => {
74 | dispatch(deleteSuccess(data));
75 | successFunc();
76 | }
77 | }
78 |
79 | export function doShowDetail(data) {
80 | return dispatch => {
81 | dispatch(showDetail(data))
82 | }
83 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {Link} from 'react-router'
3 | import {Layout, Menu, Icon, message} from 'antd';
4 | import './App.css';
5 |
6 | const {Content, Sider} = Layout;
7 | message.config({
8 | top: 200,
9 | duration: 3,
10 | });
11 |
12 | class App extends Component {
13 | render() {
14 | const items = [
15 | {iconType: 'fork', code: 'knowledge_tree', text: '知识树'},
16 | {iconType: 'calendar', code: 'course', text: '课程'},
17 | {iconType: 'area-chart', code: 'exam', text: '题目'},
18 | {iconType: 'bar-chart', code: 'teacher', text: '教师'},
19 | {iconType: 'video-camera', code: 'live', text: '直播'},
20 | {iconType: 'appstore-o', code: 'school', text: '学校'},
21 | ];
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
42 |
43 |
44 |
45 |
46 | {this.props.children}
47 |
48 |
49 |
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export default App;
57 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import {Provider} from 'react-redux'
2 | import {createStore, applyMiddleware} from 'redux'
3 | import thunk from 'redux-thunk'
4 | import React from 'react'
5 | import {Router, Route, browserHistory, IndexRoute} from 'react-router'
6 | import ReactDOM from 'react-dom'
7 | import { Form } from 'antd'
8 | import App from './App'
9 | import KnowledgeTree from './containers/knowledgeTree'
10 | import Course from './containers/course'
11 | import CourseDetail from './containers/courseDetail'
12 | import Exam from './containers/exam'
13 | import ExerciseDetail from './containers/exerciseDetail'
14 | import Teacher from './containers/teacher'
15 | import TeacherDetail from './containers/tearcherDetail'
16 | import Live from './containers/live'
17 | import LiveDetail from './containers/liveDetail'
18 | import SchoolDetail from './containers/schoolDetail'
19 | import School from './containers/school'
20 | import LoginForm from './containers/login'
21 | import appReducer from './reducers/index'
22 | import './index.css'
23 |
24 | let store = createStore(appReducer, applyMiddleware(thunk));
25 | const WrappedLoginForm = Form.create()(LoginForm);
26 | ReactDOM.render(
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | ,
49 | document.getElementById('root')
50 | );
51 |
--------------------------------------------------------------------------------
/src/components/ExerciseConent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {config} from '../config'
3 | import { varEmpty, strFindIgnoreCase } from '../utils/util';
4 |
5 | class ExerciseContent extends Component {
6 | render() { // props: text
7 | const { text} = this.props;
8 | const imgSuffixes = ['.jpg', '.gif', '.png', '.bmp'];
9 |
10 | if (varEmpty(text))
11 | return ;
12 | return (
13 |
14 | {
15 | text.split("[!").map((item, index) => {
16 | if (index === 0)
17 | return {item};
18 | else {
19 | let findImg = false;
20 | let results = imgSuffixes.map((imgSuffix, j) => {
21 | let pos = strFindIgnoreCase(item, imgSuffix + ']');
22 | if (pos > 0 && item.indexOf(']') === pos + imgSuffix.length) {
23 | let imgUrl = item.substring(0, pos + imgSuffix.length);
24 | const pathNames = imgUrl.split('/');
25 | let imgName = pathNames[pathNames.length - 1];
26 | findImg = true;
27 | return (
28 |
29 |
30 | {item.substring(pos + imgSuffix.length + 1, item.length)}
31 |
32 | );
33 | }
34 | return null;
35 | });
36 |
37 | if (findImg)
38 | return results;
39 |
40 | return (
41 |
42 | {item}
43 |
44 | );
45 | }
46 | })
47 | }
48 |
49 | );
50 | }
51 | }
52 | export default ExerciseContent
--------------------------------------------------------------------------------
/src/utils/httpReqApi.js:
--------------------------------------------------------------------------------
1 | import {config} from '../config'
2 | import {ajaxGet, ajaxPost} from './ajaxMethod'
3 |
4 | const _loginUrl = config.realHost + '/login/do/user_login';
5 | const _dictionaryUrl = config.realHost + '/app/dictionary';
6 | const _knowledgeTreeUrl = config.realHost + '/app/knowledge_tree';
7 | const _courseListUrl = config.realHost + '/app/search';
8 | const _examListUrl = _courseListUrl;
9 | const _teacherListUrl = _courseListUrl;
10 | const _liveListUrl = _courseListUrl;
11 | const _schoolListUrl = config.realHost + '/login/register/do/search_school';
12 | const _updateSchoolUrl = config.realHost + '/admin/school/update';
13 | const _register_step_Url = config.realHost + '/login/register/do/pre_update_step1';
14 | const _verifyUrl = config.realHost + '/admin/audit';
15 | const _updateResourceKnowledgeTreeUrl = config.realHost + '/admin/knowledgeTree/update';
16 |
17 | export function userLogin (data, callback){
18 | return ajaxPost(_loginUrl, {}, data, callback)
19 | }
20 |
21 | export function fetchKnowledgeTree(requestInfo, callback) {
22 | return ajaxPost(_knowledgeTreeUrl, {}, requestInfo, callback)
23 | }
24 |
25 | export function updateKnowledgeTree(requestInfo, callback) {
26 | return ajaxPost(_knowledgeTreeUrl + '/update', {}, requestInfo, callback)
27 | }
28 |
29 | export function fetchDictionary(regionId, callback) {
30 | return ajaxPost(_dictionaryUrl, {}, {regionId}, callback)
31 | }
32 |
33 | export function fetchCourseList(requestInfo, callback) {
34 | return ajaxPost(_courseListUrl, {}, requestInfo, callback)
35 | }
36 |
37 | export function fetchExamList(requestInfo, callback) {
38 | return ajaxPost(_examListUrl, {}, requestInfo, callback)
39 | }
40 |
41 | export function fetchTeacherList(requestInfo, callback) {
42 | return ajaxPost(_teacherListUrl, {}, requestInfo, callback)
43 | }
44 |
45 | export function fetchLiveList(requestInfo, callback) {
46 | return ajaxPost(_liveListUrl, {}, requestInfo, callback)
47 | }
48 |
49 | export function fetchSchoolList(requestInfo, callback) {
50 | return ajaxPost(_schoolListUrl, {}, requestInfo, callback)
51 | }
52 |
53 | export function updateSchool(requestInfo, callback) {
54 | return ajaxPost(_updateSchoolUrl, {}, requestInfo, callback)
55 | }
56 |
57 | export function priUpdateStep(requestInfo, callback) {
58 | return ajaxPost(_register_step_Url, {}, requestInfo, callback)
59 | }
60 |
61 | export function auditResource(requestInfo, callback) {
62 | return ajaxPost(_verifyUrl, {}, requestInfo, callback)
63 | }
64 |
65 | export function udpateResourceKnowledgeTree(requestInfo) {
66 | return ajaxPost(_updateResourceKnowledgeTreeUrl, {}, requestInfo, null);
67 | }
--------------------------------------------------------------------------------
/src/actions/school.action.js:
--------------------------------------------------------------------------------
1 | import {fetchSchoolList, updateSchool} from '../utils/httpReqApi'
2 |
3 | export const FETCH_SCHOOL_LIST = 'fetch_school_list';
4 | export const FETCH_SCHOOL_SUCCESS = 'fetch_school_success';
5 | export const FETCH_SCHOOL_FAILED = 'fetch_school_failed';
6 | export const DELETE_SCHOOL_SUCCESS = 'delete_school_success';
7 | export const DELETE_SCHOOL_FAILED = 'delete_school_failed';
8 | export const SHOW_SCHOOL_DETAIL = 'show_school_detail';
9 |
10 | const doFetch = () => {
11 | return {
12 | type: FETCH_SCHOOL_LIST,
13 | }
14 | };
15 | const doSuccess = (response) => {
16 | return {
17 | type: FETCH_SCHOOL_SUCCESS,
18 | response: response
19 | }
20 | };
21 |
22 | const doFailed = (response) => {
23 | return {
24 | type: FETCH_SCHOOL_FAILED,
25 | response: response
26 | }
27 | };
28 |
29 | const deleteSuccess = (index) => {
30 | return {
31 | type: DELETE_SCHOOL_SUCCESS,
32 | index: index
33 | }
34 | }
35 |
36 | const deleteFailed = (response) => {
37 | return {
38 | type: DELETE_SCHOOL_FAILED,
39 | index: response
40 | }
41 | }
42 |
43 | const showDetail = (school) => {
44 | return {
45 | type: SHOW_SCHOOL_DETAIL,
46 | school: school
47 | }
48 | };
49 |
50 | export function doFetchSchoolList(data, successFunc, failedFuc) {
51 | return dispatch => {
52 | dispatch(doFetch(data));
53 |
54 | return fetchSchoolList(data).then(
55 | response => {
56 | if (response.code === undefined) {
57 | alert(`No code in result of post: fetch course list`);
58 | return;
59 | }
60 |
61 | if(response.code === 0 && response.content) {
62 | dispatch(doSuccess(response));
63 | } else {
64 | dispatch(doFailed(response));
65 | failedFuc(response.message);
66 | }
67 | }
68 | );
69 | }
70 | }
71 |
72 | export function doDeleteSchool(data, successFunc, failedFunc) {
73 | return dispatch => {
74 | dispatch(deleteSuccess(data));
75 | successFunc();
76 | }
77 | }
78 |
79 | export function doShowDetail(data) {
80 | return dispatch => {
81 | dispatch(showDetail(data))
82 | }
83 | }
84 |
85 | export function doUpdateSchool(data, successFunc, failedFunc) {
86 | return updateSchool(data).then(
87 | response => {
88 | if (response.code === undefined) {
89 | alert(`更新学校失败!`);
90 | return;
91 | }
92 |
93 | if (response.code === 0) {
94 | successFunc()
95 | } else {
96 | failedFunc(response.message);
97 | }
98 | }
99 | )
100 | }
--------------------------------------------------------------------------------
/src/actions/knowledge.ation.js:
--------------------------------------------------------------------------------
1 | import {fetchDictionary, fetchKnowledgeTree, updateKnowledgeTree} from '../utils/httpReqApi'
2 |
3 | export const FETCH_DICTIONARY = 'fetch_dictionary';
4 | export const FETCH_DICTIONARY_SUCCESS = 'fetch_dictionary_success';
5 | export const FETCH_DICTIONARY_FAILED = 'fetch_dictionary_failed';
6 | export const FETCH_KNOWLEDGE_TREE_SUCCESS = 'fetch_knowledge_tree_success';
7 |
8 | const doFetch = () => {
9 | return {
10 | type: FETCH_DICTIONARY,
11 | }
12 | };
13 | const doSuccess = (response) => {
14 | return {
15 | type: FETCH_DICTIONARY_SUCCESS,
16 | response: response
17 | }
18 | };
19 |
20 | const fetchKnowledgeTreeSuccess = (response) => {
21 | return {
22 | type: FETCH_KNOWLEDGE_TREE_SUCCESS,
23 | response: response
24 | }
25 | };
26 |
27 | const doFailed = (response) => {
28 | return {
29 | type: FETCH_DICTIONARY_FAILED,
30 | response: response
31 | }
32 | };
33 |
34 | export function doFetchKnowledgeTree(requestInfo, successFunc, failedFunc) {
35 | return dispatch => {
36 | return fetchKnowledgeTree (requestInfo).then(
37 | response => {
38 | if (response.code === undefined) {
39 | alert(`获取知识树失败!`);
40 | dispatch(doFailed(response));
41 | return;
42 | }
43 |
44 | if (response.code === 0) {
45 | dispatch(fetchKnowledgeTreeSuccess(response))
46 | } else {
47 | dispatch(doFailed(response));
48 | failedFunc(response.message);
49 | }
50 | }
51 | )
52 | }
53 | }
54 |
55 | export function doFetchDictionary (regionId, successFunc, failedFuc){
56 | return dispatch => {
57 | dispatch(doFetch());
58 |
59 | return fetchDictionary(regionId).then(
60 | response => {
61 | if (response.code === undefined) {
62 | alert(`No code in result of post: fetchKnowledgeTree`);
63 | dispatch(doFailed(response));
64 | return;
65 | }
66 | if (response.code === 0) {
67 | dispatch(doSuccess(response))
68 | } else {
69 | dispatch(doFailed(response));
70 | failedFuc(response.message);
71 | }
72 | }
73 | );
74 | }
75 | }
76 |
77 | export function doUpdateKnowledgeTree(requestInfo, successFunc, failedFunc) {
78 | return updateKnowledgeTree(requestInfo).then(
79 | response => {
80 | if (response.code === undefined) {
81 | alert(`更新知识树失败!`);
82 | return;
83 | }
84 |
85 | if (response.code === 0) {
86 | successFunc()
87 | } else {
88 | failedFunc(response.message);
89 | }
90 | }
91 | )
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/src/components/knowledgeTreeItem.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {getKnowledgeTreePath} from '../utils/TreeToo'
4 | import KnowledgeTreeLinePrivate from './knowledgeTreeLine'
5 | import {Icon, Tooltip} from 'antd'
6 |
7 | class KnowledgeTreeItem extends Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {
11 | knowledgeTreePaths: []
12 | }
13 | }
14 |
15 | setKnowledgeTreePaths(knowledgeTreeIds) {
16 | const {dictionary} = this.props.dictionary;
17 | const knowledgeTreePaths = knowledgeTreeIds.map(
18 | (knowledgeTreeId, index) => getKnowledgeTreePath(dictionary.knowledgeTree, knowledgeTreeId)
19 | );
20 |
21 | this.setState({knowledgeTreePaths: knowledgeTreePaths,});
22 | }
23 |
24 | handleKnowledgeTreeChange(knowledgeTreeId, index) {
25 | this.props.knowledgeTreeIds[index] = knowledgeTreeId;
26 | this.setKnowledgeTreePaths(this.props.knowledgeTreeIds);
27 | }
28 |
29 | removeKnowledgeTree(index) {
30 | this.props.knowledgeTreeIds.splice(index, 1);
31 | this.setKnowledgeTreePaths(this.props.knowledgeTreeIds);
32 | }
33 |
34 | componentWillReceiveProps(nextProps) {
35 | console.log('检测 props 属性的改变'+ nextProps);
36 | this.setKnowledgeTreePaths(nextProps.knowledgeTreeIds);
37 | }
38 |
39 | componentWillMount() {
40 | this.setKnowledgeTreePaths(this.props.knowledgeTreeIds);
41 | }
42 |
43 | render() { //knowledgeTreeIds, dictionary
44 | return (
45 |
46 | {this.state.knowledgeTreePaths.map(
47 | (knowledgeTreePath, index) => (
48 |
49 |
50 |
57 | {!this.props.disabled ?
58 |
59 | this.removeKnowledgeTree(index)}>
60 |
61 |
62 |
63 | : ''
64 | }
65 |
66 | )
67 | )}
68 |
69 | )
70 | }
71 | }
72 |
73 | const mapStateToProps = (state) => {
74 | return {
75 | dictionary: state.dictionary,
76 | }
77 | };
78 |
79 | export default connect(mapStateToProps)(KnowledgeTreeItem)
80 |
--------------------------------------------------------------------------------
/src/containers/login.js:
--------------------------------------------------------------------------------
1 | import { Form, Icon, Input, Button } from 'antd';
2 | import { message } from 'antd';
3 | import React, {Component } from 'react'
4 | import {connect} from 'react-redux'
5 | import {browserHistory} from 'react-router'
6 | import {userList} from '../config'
7 | import {fetchLogin}from '../actions/login.action'
8 | import '../index.css'
9 | const FormItem = Form.Item;
10 |
11 |
12 | message.config({
13 | top: 200,
14 | duration: 3,
15 | });
16 |
17 | class LoginForm extends Component {
18 |
19 | loginSuccessFunc() {
20 | browserHistory.push({pathname: '/management'});
21 | }
22 | loginFailedFuc(msg){
23 | message.error(msg);
24 | }
25 |
26 | handleSubmit = (e) => {
27 | const { dispatch } = this.props;
28 | e.preventDefault();
29 | this.props.form.validateFields((err, values) => {
30 | if (!err) {
31 | console.log('Received values of form: ', values);
32 | }
33 |
34 | if (userList.hasOwnProperty(values.userName)) {
35 | fetchLogin(values, this.loginSuccessFunc, this.loginFailedFuc)(dispatch);
36 | } else {
37 | message.error('登录失败,请检查用户名密码!')
38 | }
39 |
40 | });
41 | };
42 |
43 | render() {
44 | const { getFieldDecorator } = this.props.form;
45 | return (
46 |
74 | );
75 | }
76 | }
77 |
78 | function mapStateToProps(state) {
79 | return {
80 | message: state && state.message,
81 | userHasLogin: state && state.userHasLogin
82 | }
83 | }
84 |
85 | export default connect(mapStateToProps)(LoginForm)
86 |
--------------------------------------------------------------------------------
/src/containers/school.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import {getSchoolColumns} from '../utils/tableColumnsDef'
5 | import {doFetchSchoolList, doDeleteSchool} from '../actions/school.action'
6 | import SearchBox from '../components/SearchBox'
7 | import {doShowDetail} from '../actions/school.action'
8 | import {TABLE_PAGE_SIZE, Biz_Target_Status} from '../utils/constants'
9 | import {Table, message, Spin, Row, Select} from 'antd'
10 | import '../App.css'
11 | const Option = Select.Option;
12 |
13 | class School extends Component {
14 | constructor(props) {
15 | super(props);
16 | this.searchKey = null;
17 | this.verified = false;
18 | }
19 |
20 | editRecord(index) {
21 | console.log('edit course record: ' + index);
22 | doShowDetail(index);
23 | const {schoolList} = this.props.school;
24 | const {dispatch} = this.props;
25 |
26 | doShowDetail(schoolList[index])(dispatch);
27 | browserHistory.push({
28 | pathname: `/management/school/${schoolList[index].id}`
29 | })
30 | }
31 | deleteRecord(index) {
32 | const { dispatch } = this.props;
33 | console.log('delete school record: ' + index);
34 | const successFunc = () => message.success('delete school record success');
35 | doDeleteSchool(index, successFunc, null)(dispatch);
36 | }
37 |
38 | searchFunc(value) {
39 | console.log('searching school: ' + value);
40 | this.searchKey = value;
41 | this.loadData();
42 | }
43 |
44 | onVerifiedStatusChange(value) {
45 | console.log(`check status change to: ${value}`)
46 | this.verified = value === '0' ? false : value === '1' ? true : null;
47 | this.loadData();
48 | }
49 |
50 | loadData() {
51 | const {dispatch, userState} = this.props;
52 | const requestInfo = {
53 | 'userToken': userState.userInfo.userToken,
54 | 'provinceId': userState.userInfo.regionId,
55 | 'searchKey': this.searchKey,
56 | 'verified': this.verified,
57 | };
58 |
59 | doFetchSchoolList(requestInfo, null, (msg)=> {message.error(msg)})(dispatch);
60 | }
61 |
62 | componentDidMount() {
63 | this.loadData();
64 | }
65 |
66 | render() {
67 | const {schoolList, loading} = this.props.school;
68 |
69 | return (
70 |
71 |
72 |
73 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
85 | {
86 | loading ? (
87 |
88 |
89 |
90 | ) : (
91 |
record.id}
92 | pagination={false}
93 | columns={getSchoolColumns(this.editRecord.bind(this), this.deleteRecord.bind(this))}
94 | />
95 | )
96 | }
97 |
98 |
99 | )
100 | }
101 | }
102 |
103 | function mapStateToProps(state) {
104 | return {
105 | school: state.school,
106 | userState: state.login,
107 | }
108 | }
109 | export default connect(mapStateToProps)(School)
110 |
--------------------------------------------------------------------------------
/src/containers/teacher.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import FilterHeader from '../components/filterHeader'
5 | import {doFetchTeacherList, doDeleteTeacher, doShowDetail} from '../actions/teacher.action'
6 | import {getTeacherColumns} from '../utils/tableColumnsDef'
7 | import {TABLE_PAGE_SIZE, Biz_Target_Type} from '../utils/constants'
8 | import {Table, message, Spin, Button} from 'antd'
9 | import '../App.css'
10 |
11 | class Teacher extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.offset= 0;
15 | this.limit= TABLE_PAGE_SIZE;
16 | this.knowledgeTreeId = null;
17 | this.verified = null;
18 | }
19 |
20 | editRecord(index) {
21 | console.log('edit course record: ' + index);
22 | const {teacherList} = this.props.teacher;
23 | const {dispatch} = this.props;
24 |
25 | doShowDetail(teacherList[index])(dispatch);
26 | browserHistory.push({
27 | pathname: `/management/teacher/${teacherList[index].userId}`
28 | })
29 | }
30 | deleteRecord(index) {
31 | const { dispatch } = this.props;
32 | console.log('delete teacher record: ' + index);
33 | const successFunc = () => message.success('delete teacher record success');
34 | doDeleteTeacher(index, successFunc, null)(dispatch);
35 | }
36 |
37 | filterChangeCallback(filterObj) {
38 | const {knowledgeTreeId, verified} = filterObj;
39 | this.knowledgeTreeId = knowledgeTreeId;
40 | this.verified = verified;
41 | this.offset = 0;
42 | ``
43 | this.loadData();
44 | }
45 |
46 |
47 | loadData() {
48 | const {dispatch, userState} = this.props;
49 | const requestInfo = {
50 | 'userToken': userState.userInfo && userState.userInfo.userToken,
51 | 'bizTargetType': Biz_Target_Type.TEACHER,
52 | 'knowledgeTreeId': this.knowledgeTreeId,
53 | 'bizTargetStatus': this.verified,
54 | 'commonTeacherRegionId': userState.userInfo && userState.userInfo.regionId,
55 | 'offset': this.offset,
56 | 'limit': this.limit,
57 | 'fromAdmin': !this.verified || null
58 | };
59 |
60 | doFetchTeacherList(requestInfo, null, (msg)=> {message.error(msg)})(dispatch);
61 | }
62 |
63 | prevPage() {
64 | this.offset -= this.limit;
65 | this.loadData();
66 | }
67 | nextPage() {
68 | this.offset += this.limit;
69 | this.loadData();
70 | }
71 |
72 | componentDidMount() {
73 | this.offset = 0;
74 | this.loadData();
75 | }
76 |
77 | render() {
78 | const {dictionary} = this.props.dictionary;
79 | const {teacherList, loading} = this.props.teacher;
80 |
81 | return (
82 |
83 |
85 |
86 | {
87 | loading ? (
88 |
89 | ) : (
90 |
record.userId}
91 | pagination={false}
92 | columns={getTeacherColumns(dictionary, this.editRecord.bind(this),
93 | this.deleteRecord.bind(this))
94 | }
95 | />
96 | )
97 | }
98 |
99 |
100 |
101 |
102 |
103 |
104 | )
105 | }
106 | }
107 |
108 | function mapStateToProps(state) {
109 | return {
110 | teacher: state.teacher,
111 | userState: state.login,
112 | dictionary: state.dictionary,
113 | }
114 | }
115 | export default connect(mapStateToProps)(Teacher)
116 |
--------------------------------------------------------------------------------
/src/containers/exam.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import FilterHeader from '../components/filterHeader'
5 | import {doFetchExamList, doDeleteExam, doShowDetail} from '../actions/exam.action'
6 | import {getExamColumns} from '../utils/tableColumnsDef'
7 | import {TABLE_PAGE_SIZE, Biz_Target_Type} from '../utils/constants'
8 | import {Table, message, Spin, Button} from 'antd'
9 | import '../App.css'
10 |
11 | class Exam extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.offset= 0;
15 | this.limit= TABLE_PAGE_SIZE;
16 | this.knowledgeTreeId = null;
17 | this.verified = null;
18 | }
19 |
20 | editRecord(index) {
21 | console.log('edit exam record: ' + index);
22 | const {examList} = this.props.exam;
23 | const {dispatch} = this.props;
24 |
25 | doShowDetail(examList[index])(dispatch);
26 | browserHistory.push({
27 | pathname: `/management/exercise/${examList[index].exerciseItemId}`
28 | })
29 | }
30 | deleteRecord(index) {
31 | const { dispatch } = this.props;
32 | console.log('delete course record: ' + index);
33 | const successFunc = () => message.success('delete course record success');
34 | doDeleteExam(index, successFunc, null)(dispatch);
35 | }
36 |
37 | filterChangeCallback(filterObj) {
38 | const {knowledgeTreeId, verified} = filterObj;
39 | this.knowledgeTreeId = knowledgeTreeId;
40 | this.verified = verified;
41 | this.offset = 0;
42 |
43 | this.loadData();
44 | }
45 |
46 | loadData() {
47 | const {dispatch, userState} = this.props;
48 | const requestInfo = {
49 | 'regionId': userState.userInfo && userState.userInfo.regionId,
50 | 'userToken': userState.userInfo && userState.userInfo.userToken,
51 | 'bizTargetType': Biz_Target_Type.EXERCISE,
52 | 'searchKey': this.searchKey,
53 | 'knowledgeTreeId': this.knowledgeTreeId,
54 | 'bizTargetStatus': this.verified,
55 | 'offset': this.offset,
56 | 'limit': this.limit,
57 | 'fromAdmin': !this.verified || null
58 | };
59 | doFetchExamList(requestInfo, null, (msg)=> {message.error(msg)})(dispatch);
60 | }
61 |
62 | prevPage() {
63 | this.offset -= this.limit;
64 | this.loadData();
65 | }
66 | nextPage() {
67 | this.offset += this.limit;
68 | this.loadData();
69 | }
70 |
71 | componentDidMount() {
72 | this.offset = 0;
73 | this.loadData();
74 | }
75 |
76 | render() {
77 | const {dictionary} = this.props.dictionary;
78 | const {examList, loading} = this.props.exam;
79 |
80 | return (
81 |
82 |
84 |
85 | {
86 | loading ? (
87 |
88 | ) : (
89 |
record.exerciseItemId}
90 | pagination={false}
91 | columns={
92 | getExamColumns(dictionary.knowledgeTree,
93 | this.editRecord.bind(this),
94 | this.deleteRecord.bind(this))
95 | }
96 | />
97 | )
98 | }
99 |
100 |
101 |
102 |
103 |
104 |
105 | )
106 | }
107 | }
108 |
109 | function mapStateToProps(state) {
110 | return {
111 | exam: state.exam,
112 | userState: state.login,
113 | dictionary: state.dictionary,
114 | }
115 | }
116 | export default connect(mapStateToProps)(Exam)
117 |
--------------------------------------------------------------------------------
/src/containers/live.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import FilterHeader from '../components/filterHeader'
5 | import {doFetchLiveList, doDeleteLive, doShowDetail} from '../actions/live.action'
6 | import {getLiveColumns} from '../utils/tableColumnsDef'
7 | import {TABLE_PAGE_SIZE, Biz_Target_Type} from '../utils/constants'
8 | import {Table, message, Spin, Button} from 'antd'
9 | import '../App.css'
10 |
11 |
12 | class Live extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.offset= 0;
16 | this.limit= TABLE_PAGE_SIZE;
17 | this.knowledgeTreeId = null;
18 | this.searchKey = null;
19 | this.verified = null;
20 | }
21 |
22 | editRecord(index) {
23 | console.log('edit course record: ' + index);
24 | const {liveList} = this.props.live;
25 | const {dispatch} = this.props;
26 |
27 | doShowDetail(liveList[index])(dispatch);
28 | browserHistory.push({
29 | pathname: `/management/live/${liveList[index].liveId}`
30 | })
31 | }
32 | deleteRecord(index) {
33 | const { dispatch } = this.props;
34 | console.log('delete live record: ' + index);
35 | const successFunc = () => message.success('delete live record success');
36 | doDeleteLive(index, successFunc, null)(dispatch);
37 | }
38 |
39 | filterChangeCallback(filterObj) {
40 | const {knowledgeTreeId, searchKey, verified} = filterObj;
41 | this.knowledgeTreeId = knowledgeTreeId;
42 | this.searchKey = searchKey;
43 | this.verified = verified;
44 | this.offset = 0;
45 |
46 | this.loadData();
47 | }
48 |
49 | loadData() {
50 | const {dispatch, userState} = this.props;
51 | const requestInfo = {
52 | 'userToken': userState.userInfo && userState.userInfo.userToken,
53 | 'regionId': userState.userInfo && userState.userInfo.regionId,
54 | 'bizTargetType': Biz_Target_Type.LIVE,
55 | 'knowledgeTreeId': this.knowledgeTreeId,
56 | 'searchKey': this.searchKey,
57 | 'bizTargetStatus': this.verified,
58 | 'offset': this.offset,
59 | 'limit': this.limit,
60 | 'fromAdmin': !this.verified || null
61 | };
62 |
63 | doFetchLiveList(requestInfo, null, (msg)=> {message.error(msg)})(dispatch);
64 | }
65 |
66 | prevPage() {
67 | this.offset -= this.limit;
68 | this.loadData();
69 | }
70 | nextPage() {
71 | this.offset += this.limit;
72 | this.loadData();
73 | }
74 |
75 | componentDidMount() {
76 | this.offset = 0;
77 | this.loadData();
78 | }
79 |
80 | render() {
81 | const {dictionary} = this.props.dictionary;
82 | const {liveList, loading} = this.props.live;
83 |
84 | return (
85 |
86 |
88 |
89 | {
90 | loading ? (
91 |
92 | ) : (
93 |
record.liveId}
94 | pagination={false}
95 | columns={
96 | getLiveColumns(dictionary.knowledgeTree,
97 | this.editRecord.bind(this),
98 | this.deleteRecord.bind(this))
99 | }
100 | />
101 | )
102 | }
103 |
104 |
105 |
106 |
107 |
108 |
109 | )
110 | }
111 | }
112 |
113 | function mapStateToProps(state) {
114 | return {
115 | live: state.live,
116 | userState: state.login,
117 | dictionary: state.dictionary,
118 | }
119 | }
120 | export default connect(mapStateToProps)(Live)
121 |
--------------------------------------------------------------------------------
/src/containers/course.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import FilterHeader from '../components/filterHeader'
5 | import {doFetchCourseList, doDeleteCourse, doShowDetail} from '../actions/course.action'
6 | import {getCourseColumns} from '../utils/tableColumnsDef'
7 | import {TABLE_PAGE_SIZE, Biz_Target_Type} from '../utils/constants'
8 | import {Table, message, Spin, Button} from 'antd'
9 | import '../App.css'
10 |
11 | class Course extends Component {
12 | constructor(props) {
13 | super(props);
14 | this.offset = 0;
15 | this.limit = TABLE_PAGE_SIZE;
16 | this.knowledgeTreeId = null;
17 | this.searchKey = null;
18 | this.verified = null;
19 | }
20 |
21 | editRecord(index) {
22 | console.log('edit course record: ' + index);
23 | const {courseList} = this.props.course;
24 | const {dispatch} = this.props;
25 |
26 | doShowDetail(courseList[index])(dispatch);
27 | browserHistory.push({
28 | pathname: `/management/course/${courseList[index].id}`
29 | })
30 | }
31 |
32 | deleteRecord(index) {
33 | const {dispatch} = this.props;
34 | console.log('delete course record: ' + index);
35 | const successFunc = () => message.success('delete course record success');
36 | doDeleteCourse(index, successFunc, null)(dispatch);
37 | }
38 |
39 | filterChangeCallback(filterObj) {
40 | const {knowledgeTreeId, searchKey, verified} = filterObj;
41 | this.knowledgeTreeId = knowledgeTreeId;
42 | this.searchKey = searchKey;
43 | this.verified = verified;
44 | this.offset = 0;
45 |
46 | this.loadData();
47 | }
48 |
49 | loadData() {
50 | const {dispatch, userState} = this.props;
51 | const requestInfo = {
52 | 'userToken': userState.userInfo && userState.userInfo.userToken,
53 | 'bizTargetType': Biz_Target_Type.COURSE,
54 | 'knowledgeTreeId': this.knowledgeTreeId,
55 | 'searchKey': this.searchKey,
56 | 'bizTargetStatus': this.verified,
57 | 'regionId': userState.userInfo && userState.userInfo.regionId,
58 | 'offset': this.offset,
59 | 'limit': this.limit,
60 | 'fromAdmin': !this.verified || null
61 | };
62 | doFetchCourseList(requestInfo, null, (msg) => message.error(msg))(dispatch);
63 | }
64 |
65 | prevPage() {
66 | this.offset -= this.limit;
67 | this.loadData();
68 | }
69 | nextPage() {
70 | this.offset += this.limit;
71 | this.loadData();
72 | }
73 |
74 | componentDidMount() {
75 | this.offset = 0;
76 | this.loadData();
77 | }
78 |
79 | render() {
80 | const {dictionary} = this.props.dictionary;
81 | const {courseList, loading} = this.props.course;
82 |
83 | return (
84 |
85 |
87 |
88 | {
89 | loading ? (
90 |
91 | ) : (
92 |
record.id}
93 | pagination={false}
94 | columns={
95 | getCourseColumns(dictionary.knowledgeTree,
96 | this.editRecord.bind(this),
97 | this.deleteRecord.bind(this))
98 | }
99 | />
100 | )
101 | }
102 |
103 |
104 |
105 |
106 |
107 |
108 | )
109 | }
110 | }
111 |
112 | function mapStateToProps(state) {
113 | return {
114 | course: state.course,
115 | userState: state.login,
116 | dictionary: state.dictionary,
117 | }
118 | }
119 | export default connect(mapStateToProps)(Course)
120 |
--------------------------------------------------------------------------------
/src/containers/knowledgeTree.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import Tree from '../components/tree'
4 | import {doFetchDictionary, doFetchKnowledgeTree, doUpdateKnowledgeTree} from '../actions/knowledge.ation'
5 | import {getFatherNodePathByKtId} from '../utils/TreeToo'
6 | import {message, Spin, Modal, Input} from 'antd'
7 |
8 |
9 | class KnowledgeTree extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | modalVisible: false,
14 | newNode: {
15 | code: '',
16 | display: '',
17 | visible: true
18 | }
19 | };
20 | this.actionNode = null;
21 | }
22 |
23 | updateKnowledgeTree(subjectNode) {
24 | const { userState, dictionary} = this.props;
25 | const requestInfo = {
26 | regionId: userState.userInfo.regionId,
27 | userToken: userState.userInfo.userToken,
28 | subjectId: subjectNode.id,
29 | tree: subjectNode.children
30 | };
31 |
32 | doUpdateKnowledgeTree(requestInfo, this.getKnowledgeTree.bind(this), (msg)=> {message.error(msg)})
33 | }
34 |
35 | deleteTreeNode(node) {
36 | console.log('delete tree node: ' + node);
37 | const nodePath = getFatherNodePathByKtId(this.props.dictionary.knowledgeTree, node.id);
38 | const fatherNode = nodePath[nodePath.length -2];
39 |
40 | for (let i= 0; i < fatherNode.children.length; i++) {
41 | if (fatherNode.children[i].id === node.id) {
42 | fatherNode.children.splice(i, 1);
43 | break;
44 | }
45 | }
46 | this.updateKnowledgeTree(nodePath[1]);
47 | }
48 |
49 | addTreeNode(node, newNode) {
50 | console.log('add tree node: ' + node);
51 | const nodePath = getFatherNodePathByKtId(this.props.dictionary.knowledgeTree, node.id);
52 | if (!node.children) {
53 | node.children = [];
54 | }
55 | node.children.push(newNode);
56 | this.updateKnowledgeTree(nodePath[1]);
57 | }
58 |
59 | closeTreeNode(node, visible) {
60 | node.visible = visible;
61 | const nodePath = getFatherNodePathByKtId(this.props.dictionary.knowledgeTree, node.id);
62 | this.updateKnowledgeTree(nodePath[1]);
63 | }
64 |
65 | showAddNodeModal(node) {
66 | this.actionNode = node;
67 | this.setState({modalVisible: true,});
68 | }
69 |
70 | modalCancelHandler = () => {
71 | this.setState({modalVisible: false});
72 | }
73 |
74 | modalOkHandler = () => {
75 | const code = document.getElementById('nodeCode').value;
76 | const display = document.getElementById('nodeDisplay').value;
77 | if (!code || !display) {
78 | message.warn('标题或名称不能为空!');
79 | return;
80 | }
81 |
82 | this.addTreeNode(this.actionNode, {code: code, display: display, visible: true});
83 | this.modalCancelHandler();
84 | }
85 |
86 | getKnowledgeTree() {
87 | const { dispatch } = this.props;
88 | const { userInfo } = this.props.userState;
89 |
90 | doFetchKnowledgeTree({
91 | regionId: userInfo.regionId,
92 | showAll: true
93 | }, null, (msg) => {message.error(msg)})(dispatch);
94 | }
95 |
96 | componentDidMount() {
97 | const { dispatch } = this.props;
98 | const { userInfo } = this.props.userState;
99 | this.getKnowledgeTree();
100 | doFetchDictionary(userInfo.regionId, null, (msg)=> {message.error(msg)})(dispatch)
101 | }
102 |
103 | render() {
104 | const {knowledgeTree, loading} = this.props.dictionary;
105 | const {modalVisible} = this.state
106 | return (
107 |
108 | {
109 | loading ? (
110 |
111 |
112 |
113 | ) : (
114 |
119 | )
120 | }
121 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | )
136 | }
137 | }
138 |
139 | function mapStateToProps(state) {
140 | return {
141 | userState: state.login,
142 | dictionary: state.dictionary,
143 | }
144 | }
145 | export default connect(mapStateToProps)(KnowledgeTree)
146 |
--------------------------------------------------------------------------------
/src/containers/schoolDetail.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import {Biz_Target_Type} from '../utils/constants'
5 | import {doAuditResource} from '../actions/auditResource.action'
6 | import {doUpdateSchool} from '../actions/school.action'
7 | import {Button, Icon, Input, message, Radio} from 'antd'
8 | import '../App.css'
9 | const RadioGroup = Radio.Group;
10 |
11 |
12 | class SchoolDetail extends Component {
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | edit: false,
17 | passed: false
18 | }
19 | }
20 |
21 | closePage() {
22 | browserHistory.goBack();
23 | }
24 |
25 | onCheckStatusChange(e) {
26 | this.setState({passed: e.target.value})
27 | }
28 |
29 | onEditAddress() {
30 | this.setState({edit: true});
31 | }
32 |
33 | verifySchool() {
34 | const textArea = document.getElementById('comment');
35 | if (!this.state.passed) {
36 | if (textArea.value === '') {
37 | message.warning('请输入未通过原因');
38 | return;
39 | }
40 | console.log(textArea.value);
41 | }
42 |
43 | const {userState, detail} = this.props;
44 | const requestInfo = {
45 | 'userToken': userState.userInfo.userToken,
46 | 'bizTargetType': Biz_Target_Type.SCHOOL,
47 | 'auditPassed': this.state.passed,
48 | 'targetId': detail.school.id,
49 | 'auditComment': this.state.passed ? '' : textArea.value
50 | };
51 |
52 | doAuditResource(requestInfo, this.closePage.bind(this), (msg)=> {message.error(msg)});
53 | }
54 |
55 | onConfirmBtnClick() {
56 | const {school} = this.props.detail;
57 | if (school.verified) {
58 | this.closePage();
59 | return ;
60 | }
61 |
62 | if(this.state.edit) {
63 | const schoolName = document.getElementById('schoolName').value;
64 | if (school.name !== schoolName) {
65 | const { userInfo} = this.props.userState;
66 |
67 | doUpdateSchool({
68 | regionId: userInfo.regionId,
69 | userToken: userInfo.userToken,
70 | id: school.id,
71 | name: schoolName,
72 | }, this.verifySchool.bind(this), (msg) => {message.error(msg)})
73 | }
74 | }
75 | this.verifySchool();
76 | }
77 |
78 | render() {
79 | const {school} = this.props.detail;
80 |
81 | return (
82 |
83 |
84 |
85 |
86 | {this.state.edit
87 | ?
88 | :
89 |
90 |
91 |
92 | }
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | {!school.verified ?
105 |
106 | this.onCheckStatusChange(e)} value={this.state.passed}>
107 | 通过
108 | 否决
109 |
110 |
111 | :
112 | }
113 |
114 |
115 |
116 | {!school.verified ?
117 |
118 |
119 |
120 |
121 |
122 | : ''
123 | }
124 |
125 | {school.verified
126 | ?
127 |
128 |
129 | :
130 |
131 |
132 |
133 | }
134 |
135 | )
136 | }
137 | }
138 | const mapStateToProps = (state) => {
139 | return {
140 | userState: state.login,
141 | detail: state.school,
142 | }
143 | };
144 |
145 | export default connect(mapStateToProps)(SchoolDetail)
--------------------------------------------------------------------------------
/src/components/knowledgeTreeLine.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {connect} from 'react-redux'
3 | import { Breadcrumb } from 'antd';
4 | import { Menu, Dropdown } from 'antd';
5 | import { varEmpty, varNotEmpty} from '../utils/util';
6 | import { getOrDefault } from '../utils/util';
7 | import { getValidTreeIdFromPath } from '../utils/TreeToo';
8 | import { findTreeNodeFromPath, ID_ALL, getNodeAll } from '../utils/TreeToo';
9 |
10 | const NodeMenu = ({ nodeItems, onMenuItemClicked }) => (
11 |
16 | );
17 |
18 | class KnowledgeTreeLineInternal extends Component {
19 | constructor(props) {
20 | super(props);
21 | this.dropdownBtns = {};
22 | this.state = {
23 | dropdownVisibles: {}
24 | }
25 | }
26 |
27 | handleVisibleChange(level, flag) {
28 | let newVisibles = { ...this.state.dropdownVisibles };
29 | newVisibles[level] = flag;
30 | this.setState({ dropdownVisibles: newVisibles });
31 | }
32 |
33 | setVisibleFalse(level) {
34 | let newVisibles = { ...this.state.dropdownVisibles };
35 | newVisibles[level] = false;
36 | this.setState({ dropdownVisibles: newVisibles });
37 | }
38 |
39 | onPathClicked(level, lastNodeId) {
40 | const parentPath = this.props.selectedIdsPath.slice(0, level);
41 | this.props.onNodeSelected([...parentPath, lastNodeId]);
42 | }
43 |
44 | handleMenuItemClicked(level, nodeItems, menuItem) {
45 | this.setVisibleFalse(level);
46 |
47 | const parentPath = this.props.selectedIdsPath.slice(0, level);
48 | const currentNode = nodeItems[menuItem.key];
49 |
50 | if (varNotEmpty(currentNode.children))
51 | this.props.onNodeSelected([...parentPath, currentNode.id + '', ID_ALL]);
52 | else
53 | this.props.onNodeSelected([...parentPath, currentNode.id + '']);
54 | }
55 |
56 | getNode(level) {
57 | return findTreeNodeFromPath(this.props.treeRootNodes, this.props.selectedIdsPath, level);
58 | }
59 |
60 | getSiblings(level) {
61 | if (level === 0)
62 | return this.props.treeRootNodes;
63 | const parent = findTreeNodeFromPath(this.props.treeRootNodes, this.props.selectedIdsPath, level - 1);
64 | if (varNotEmpty(parent.children))
65 | return parent.children;
66 | return [];
67 | }
68 |
69 | getSiblingsWithAll(level) {
70 | return [getNodeAll(level), ...this.getSiblings(level)];
71 | }
72 |
73 | render() { // props: treeRootNodes, selectedIdsPath, onNodeSelected, gradeSubjectOnly
74 | const { selectedIdsPath } = this.props;
75 |
76 | let selectedIdsPathModified = selectedIdsPath;
77 | if (this.props.gradeSubjectOnly && selectedIdsPath.length > 2)
78 | selectedIdsPathModified = [selectedIdsPath[0], selectedIdsPath[1]];
79 |
80 | return (
81 |
82 | {selectedIdsPathModified.map((id, level) => {
83 | const siblings = this.getSiblingsWithAll(level);
84 | const node = this.getNode(level);
85 | return
86 |
94 | }
95 | onVisibleChange={this.handleVisibleChange.bind(this, level)}
96 | visible={this.state.dropdownVisibles[level]}
97 | trigger={['click']} >
98 | {node.display}
99 |
100 |
101 | })}
102 |
103 | );
104 | }
105 | }
106 |
107 | class KnowledgeTreeLinePrivate extends Component {
108 | onNodeSelectedWrapper(newPath) {
109 | if (varNotEmpty(this.props.treeNodeSelectedCallback)){}
110 | this.props.treeNodeSelectedCallback(getValidTreeIdFromPath(newPath), this.props.index);
111 |
112 | if (varNotEmpty(this.props.treePathSelectedCallback))
113 | this.props.treePathSelectedCallback(newPath);
114 | }
115 | render() { // selectedIdsPath, treePathSelectedCallback, // treeNodeSelectedCallback, gradeSubjectOnly
116 | const {dictionary} = this.props.dictionary;
117 | return (
118 | varEmpty(dictionary)
119 | ?
120 | loading dictionary...
121 | :
122 |
123 |
128 |
);
129 | }
130 | }
131 |
132 | const mapStateToProps = (state) => {
133 | return {
134 | dictionary: state.dictionary,
135 | }
136 | };
137 |
138 | export default connect(mapStateToProps)(KnowledgeTreeLinePrivate)
139 |
140 |
--------------------------------------------------------------------------------
/src/components/filterHeader.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {Biz_Target_Status} from '../utils/constants'
3 | import {Row, Input, Select} from 'antd'
4 | import '../App.css'
5 |
6 | const Search = Input.Search;
7 | const Option = Select.Option;
8 |
9 | class FilterHeader extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | subjects: [],
14 | selectedGrade: 'all',
15 | selectedSubject: "all",
16 | ...this.knowledgeToMap()
17 | };
18 |
19 | this.searchCondition = {
20 | knowledgeTreeId: null,
21 | searchKey: '',
22 | verified: null
23 | }
24 | }
25 |
26 | knowledgeToMap() {
27 | const result ={};
28 | if (this.props.knowledgeTree) {
29 | this.props.knowledgeTree.forEach(item => {
30 | result[item.id] = item;
31 | });
32 | return {grades:this.props.knowledgeTree, gradMap: result}
33 | }
34 | return {grades:[], gradMap: result}
35 | }
36 |
37 | onConditionChange() {
38 | if (this.props.searchFunc) {
39 | this.props.searchFunc(this.searchCondition);
40 | }
41 | }
42 |
43 |
44 | onGradeChange(value) {
45 | console.log(`grade selected: ${value}`);
46 | this.setState({
47 | subjects: value === 'all' ? [] : (this.state.gradMap[value].children || []),
48 | selectedSubject: 'all',
49 | selectedGrade: value === 'all' ? null : value
50 | });
51 | this.searchCondition.knowledgeTreeId = value === 'all' ? null : value;
52 | this.onConditionChange();
53 | }
54 |
55 | onSubjectChange(value) {
56 | console.log(`subject selected: ${value}`);
57 | this.setState({selectedSubject: value});
58 | this.searchCondition.knowledgeTreeId = value === 'all' ? this.state.selectedGrade : value;
59 | this.onConditionChange();
60 | }
61 |
62 | onSearchSelect(value) {
63 | console.log(`search text: ${value}`)
64 | this.searchCondition.searchKey = value;
65 | this.onConditionChange();
66 | }
67 |
68 | getVerifiedStatus(value) {
69 | switch (value) {
70 | case Biz_Target_Status.SUBMITTED.toString():
71 | return Biz_Target_Status.SUBMITTED;
72 | case Biz_Target_Status.RELEASED.toString():
73 | return Biz_Target_Status.RELEASED;
74 | case Biz_Target_Status.UN_PASSED.toString():
75 | return Biz_Target_Status.UN_PASSED;
76 | default:
77 | return null;
78 | }
79 | }
80 | onVerifiedStatusChange(value) {
81 | console.log(`check status change to: ${value}`);
82 | this.searchCondition.verified = this.getVerifiedStatus(value);
83 | this.onConditionChange();
84 | }
85 |
86 | getGradeOptions() {
87 | const options = this.state.grades.map(
88 | (item, index) => ()
89 | );
90 | options.unshift();
91 | return options;
92 | }
93 |
94 | getSubjectOptions() {
95 | const options = this.state.subjects.map(
96 | (item, index) => ()
97 | );
98 |
99 | options.unshift();
100 | return options;
101 | }
102 |
103 | render() {
104 | const searchLabel = this.props.searchLabel || '课程名称:';
105 | const gradeLabel ='年级:';
106 | const subjectLabel ='科目:';
107 |
108 | const gradeOptions = this.getGradeOptions();
109 |
110 | return (
111 |
112 |
113 |
114 |
118 |
119 |
120 |
121 |
122 |
126 |
127 |
128 |
129 |
130 |
135 |
136 |
137 |
138 |
139 |
146 |
147 |
148 | )
149 | }
150 | }
151 |
152 | export default FilterHeader;
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.css';
2 | .App {
3 | text-align: center;
4 | }
5 | .margin-left-30 {
6 | margin-left: 30px;
7 | }
8 | .margin-right-30 {
9 | margin-right: 30px;
10 | }
11 |
12 | .nave-label{
13 | color: #fff;
14 | }
15 | .main-container {
16 | position: relative;
17 | overflow: initial;
18 | background-color: #ececec;
19 | }
20 | .main-wrapper {
21 | position: absolute;
22 | top: 0;
23 | bottom: 0;
24 | left: 0;
25 | right: 0;
26 | margin: 0 1px 0 15px;
27 | padding: 10px;
28 | background-color: #fff;
29 | overflow-y: auto;
30 | }
31 | .filter-header {
32 | padding: 15px 5px 15px;
33 | background-color: antiquewhite;
34 | }
35 |
36 | .filter-header label {
37 | font-size: 15px;
38 | line-height: 28px;
39 | float: left;
40 | }
41 | .filter-header .search-style{
42 | margin-left: 15px;
43 | width: 150px
44 | }
45 | .filter-header .item-warp {
46 | display: inline-block;
47 | float: left;
48 | }
49 | .filter-header .item-warp .select-style{
50 | margin-left: 15px;
51 | width: 100px
52 | }
53 | .filter-header >:first-child {
54 | display: inline-block;
55 | float: left;
56 | }
57 |
58 | .content-wrapper {
59 | position: relative;
60 | }
61 | .content-wrapper .table-style {
62 | padding: 15px 0
63 | }
64 | .content-wrapper .table-style .pagination {
65 | margin-top: 30px;
66 | }
67 | .content-wrapper .table-style .pagination button {
68 | margin-right: 50px;
69 | }
70 |
71 | .tree-ul {
72 | padding: 20px 30px 0;
73 | display: block;
74 | }
75 | .tree-ul li {
76 | padding: 15px 0 0;
77 | }
78 | .tree-ul .li-border-line {
79 | border-left: 1px dashed #000000;
80 | margin-top: 5px;
81 | }
82 | .tree-ul .togglable-down::before,
83 | .tree-ul .togglable-up::before {
84 | padding-right: 8px;
85 | }
86 | .tree-ul .togglable-down::before {
87 | content: "▼";
88 | font-size: 18px;
89 | margin-left: -9px;
90 | }
91 | .tree-ul .togglable-up::before {
92 | content: "▶";
93 | font-size: 16px;
94 | margin-left: -3px;
95 | }
96 | .tree-ul li .node {
97 | float: left;
98 | line-height: 30px;
99 | }
100 |
101 | .tree-ul li .node > label {
102 | color: #070708;
103 | float: left;
104 | font-size: 14px;
105 | font-weight: bold;
106 | cursor: pointer;
107 | }
108 | .tree-ul li .node > label:hover {
109 | color: blue;
110 | }
111 | .tree-ul li .node > .null-label {
112 | margin-left: 21px;
113 | }
114 |
115 | .tree-ul li .disabled-node {
116 | color: #dfe2cf !important;
117 | }
118 |
119 | .add-icon,
120 | .delete-icon,
121 | .close-icon,
122 | .open-icon{
123 | float: left;
124 | margin-left: 20px;
125 | font-size: 15px;
126 | cursor: pointer;
127 | }
128 |
129 | .add-icon {
130 | color: #0c06f3;
131 | }
132 | .delete-icon{
133 | color: red;
134 | }
135 | .close-icon{
136 | color: #61710a
137 | }
138 | .open-icon {
139 | color: #d0a8c9;
140 | }
141 |
142 | .clearfixed:after{
143 | display: block;
144 | content: '.';
145 | visibility: hidden;
146 | height: 0;
147 | clear:both
148 | }
149 |
150 | /* --- 布局 --- */
151 | .row-form {
152 | position: relative;
153 | padding: 0;
154 | height: 30px;
155 | margin-bottom: 15px;
156 | }
157 | .row-form .offset-1-2 { /* 向左偏移 1/2*/
158 | position: absolute;
159 | left: 50%;
160 | }
161 | .row-form .offset-1-3 { /* 向左偏移 1/3 */
162 | position: absolute;
163 | left: 33.33%;
164 | }
165 | .row-form .offset-2-3 { /* 向左偏移 2/3 */
166 | position: absolute;
167 | left: 66.66%;
168 | }
169 | .row-form > * {
170 | float: left;
171 | line-height: 30px;
172 | }
173 | .row-form .offset-1-2 > * ,
174 | .row-form .offset-1-3 > * ,
175 | .row-form .offset-2-3 > * {
176 | float: left;
177 | }
178 | /* --- 布局 --- */
179 | .row-form .margin-left-20 {
180 | margin-left: 20px;
181 | }
182 |
183 | .row-form .file-icon {
184 | margin-right: 5px;
185 | font-size: 14px;
186 | }
187 | .row-form .control-label {
188 | text-align: right;
189 | width: 120px;
190 | font-size: 14px;
191 | line-height: 30px;
192 | }
193 |
194 | .row-form .modal-control-label {
195 | text-align: right;
196 | width: 80px;
197 | font-size: 14px;
198 | line-height: 30px;
199 | }
200 | .row-form .modal-input {
201 | width: 220px;
202 | }
203 |
204 | .row-form .ant-radio-group label,
205 | .row-form .info-label {
206 | font-weight: bold;
207 | font-size: 14px;
208 | line-height: 30px;
209 | }
210 | .row-form .player-btn {
211 | font-size: 22px;
212 | border-radius: 34px;
213 | width: 36px;
214 | height: 34px;
215 | padding: 0 10px;
216 | }
217 | .row-form .live-box {
218 | width: 800px;
219 | height: 460px;
220 | margin-top: 5px;
221 | }
222 | .row-form a {
223 | font-size: 14px;
224 | }
225 | .row-form textarea {
226 | height: 80px;
227 | width: 400px;
228 | font-size: 14px;
229 | margin-top: 10px;
230 | }
231 | .row-form img {
232 | width: 240px;
233 | height: 200px;
234 | margin-top: 10px;
235 | }
236 | .row-form .add-tree{
237 | font-size: 18px;
238 | color: blue;
239 | }
240 | .row-form .remove-tree{
241 | line-height: 30px;
242 | margin-left: 30px;
243 | font-size: 18px;
244 | color: red;
245 | }
246 |
247 | .img-height {
248 | height: 220px;
249 | }
250 | .textarea-height {
251 | height: 80px
252 | }
253 | .certificate-height {
254 | height: 280px;
255 | }
256 | .live-player {
257 | height: 460px;
258 | }
259 | .item-hide {
260 | display: none;
261 | }
262 | .confirm-box {
263 | height: 30px;
264 | margin: 30px auto 0;
265 | }
266 | .confirm-box > button{
267 | margin-right: 100px;
268 | }
269 |
270 | .exercise-line {
271 | margin-bottom: 20px;
272 | }
273 |
274 | .exercise-label {
275 | font-size: 14px;
276 | text-align: right;
277 | width: 120px;
278 | float: left;
279 | }
280 |
281 | .exercise-content {
282 | margin-left: 20px;
283 | text-align: left;
284 | font-weight: bold;
285 | font-size: 14px;
286 | float: left;
287 | }
288 |
289 | .exercise-content-span {
290 | float: left;
291 | width: 600px;
292 | }
293 |
294 | .edit-pen-style {
295 | color: #0c06f3;
296 | margin-left: 30px;
297 | }
298 | .edit-pen-style:hover{
299 | cursor: pointer;
300 | }
301 |
302 | .warn-color {
303 | color: red;
304 | }
305 |
306 | .info-color {
307 | color: #56bb56;
308 | }
--------------------------------------------------------------------------------
/src/components/tree.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {Icon, Popconfirm, Tooltip} from 'antd'
3 | import {isArray, isObject} from '../utils/util'
4 |
5 | const isLeafNode = (node) => {
6 | return !( node.hasOwnProperty('children') && node.children && isArray(node.children) )
7 | };
8 |
9 | class TreeNode extends Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | unfold: false,
14 | isTreeNode: !isLeafNode(this.props.node),
15 | };
16 | }
17 |
18 | toggleSpread() {
19 | if (this.props.node.visible === false) {
20 | return;
21 | }
22 |
23 | if (this.state.isTreeNode) {
24 | this.setState({unfold: !this.state.unfold});
25 | }
26 | };
27 |
28 | getLiStyleClass () {
29 | if (!this.state.isTreeNode) {
30 | return 'null-label';
31 | }
32 | return this.state.unfold ? 'togglable-down' : 'togglable-up';
33 | }
34 |
35 | onDeleteNode() {
36 | this.props.deleteNodeHandler(this.props.node);
37 | }
38 |
39 | onAddNode() {
40 | this.props.addNodeHandler(this.props.node);
41 | }
42 |
43 | onCloseNode(visible) {
44 | // this.setState({hide: hide});
45 | if (!visible && this.state.isTreeNode && this.state.unfold) {
46 | this.setState({unfold: !this.state.unfold});
47 | }
48 |
49 | this.props.closeNodeHandler(this.props.node, visible);
50 | }
51 |
52 | getChildrenNodes() {
53 | return (
54 | {
55 | this.props.node.children.map((child, index) => {
56 | return ()
62 | })
63 | }
64 | )
65 | }
66 |
67 | render() {
68 | const display = this.props.node.display;
69 | const {node} = this.props;
70 |
71 | const deleteMsg = '确定要删除此节点: ' + display + ' ?';
72 | const addMsg = '确定要为此节点添加子节点: ' + display + '?';
73 | const closeAction = node.visible === false ? '显示' : '不可见';
74 | const closeMsg = '确定要' + closeAction + '此节点: ' + display + '?';
75 |
76 | const style = this.state.unfold ? null: {display: "none"};
77 | const labelClass = this.getLiStyleClass();
78 | return (
79 |
80 |
81 |
88 |
89 | {node.visible !== false && this.props.level !== 1
90 | ? (
91 |
93 |
94 |
95 |
96 |
97 | )
98 | : ''
99 | }
100 |
101 | {node.visible !== false && this.props.level !== 1 && this.props.level !== 2
102 | ? (
103 |
105 |
106 |
107 |
108 |
109 | )
110 | : ''
111 | }
112 |
113 | { this.props.level === 1 || this.props.level === 2 ? '' : node.visible !== false
114 | ? (
115 |
this.onCloseNode(false)}>
117 |
118 |
119 |
120 |
121 | )
122 | : (
123 |
this.onCloseNode(true)}>
125 |
126 |
127 |
128 |
129 | )
130 | }
131 |
132 |
133 | {!this.state.hide
134 | ?
135 |
136 | {this.state.isTreeNode ? this.getChildrenNodes() : null}
137 |
138 | : ''
139 | }
140 |
141 | )
142 | }
143 | }
144 |
145 | class Tree extends Component{
146 | render() {
147 | if (isArray(this.props.tree)) {
148 | // tree list [tree1, tree2]
149 | return (
150 |
151 | {this.props.tree.map((root, index) => {
152 | return (
153 |
159 | )
160 | })}
161 |
162 | )
163 | } else if (isObject(this.props.tree)) {
164 | // one tree
165 | return (
166 |
174 | )
175 | }
176 | return (
)
177 | }
178 | }
179 |
180 | export default Tree
--------------------------------------------------------------------------------
/src/containers/tearcherDetail.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import {mapGradeIdToName, mapSubjectIdToName} from '../utils/TreeToo'
5 | import {doAuditResource} from '../actions/auditResource.action'
6 | import {GENDER_FEMALE, GENDER_MALE, Biz_Target_Type} from '../utils/constants'
7 | import {Button, Input, Tabs, Radio, message, Popover} from 'antd'
8 | import '../App.css'
9 | const RadioGroup = Radio.Group;
10 | const TabPane = Tabs.TabPane;
11 |
12 | const teacherCertificates = [
13 | { "code": "idCard", "display": "身份证"},
14 | { "code": "empCard", "display": "工作证" },
15 | { "code": "quaCert", "display": "教师资格证" },
16 | { "code": "psyCert", "display": "心理学证书" },
17 | { "code": "eduCert", "display": "教育学证书" },
18 | ];
19 |
20 | class TeacherDetail extends Component {
21 | constructor(props) {
22 | super(props);
23 | this.state = {
24 | passed: false,
25 | }
26 | }
27 | onCheckStatusChange(e) {
28 | this.setState({passed: e.target.value})
29 | }
30 |
31 | closePage() {
32 | browserHistory.goBack();
33 | }
34 |
35 | auditFailed() {
36 | message.error('审批失败!');
37 | }
38 |
39 | onConfirmBtnClick() {
40 | const {teacher} = this.props.detail;
41 | if (teacher.verified) {
42 | this.closePage();
43 | return;
44 | }
45 |
46 | const textArea = document.getElementById('comment');
47 | if (!this.state.passed) {
48 | if (textArea.value === '') {
49 | message.warning('请输入未通过原因');
50 | return;
51 | }
52 | }
53 |
54 | const {detail, userState} = this.props;
55 | const requestInfo = {
56 | auditComment: textArea.value,
57 | auditPassed: this.state.passed,
58 | bizTargetType: Biz_Target_Type.TEACHER,
59 | targetId: detail.teacher.userId,
60 | userToken: userState.userInfo.userToken
61 | };
62 |
63 | doAuditResource(requestInfo, this.closePage, this.auditFailed);
64 | }
65 |
66 | certificateRender() {
67 | const {teacher} = this.props.detail;
68 | return (
69 |
70 | {teacherCertificates.map(certificate => {
71 | return (
72 |
73 | } trigger="click">
74 | {teacher[certificate.code] ?
75 |
76 | : ''
77 | }
78 |
79 |
80 | )
81 | })}
82 |
83 | )
84 | }
85 |
86 | render() {
87 | const {teacher} = this.props.detail;
88 | const {dictionary} = this.props.dictionary;
89 |
90 | return (
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | {this.certificateRender()}
140 |
141 |
142 |
143 |
144 |
145 | {!teacher.verified ?
146 |
147 | this.onCheckStatusChange(e)} value={this.state.passed}>
148 | 通过
149 | 否决
150 |
151 |
152 | :
153 | }
154 |
155 |
156 |
157 | {!teacher.verified ?
158 |
159 |
160 |
161 |
162 |
163 | : ''
164 | }
165 |
166 | {teacher.verified
167 | ?
168 |
169 |
170 | :
171 |
172 |
173 |
174 | }
175 |
176 | )
177 | }
178 | }
179 | const mapStateToProps = (state) => {
180 | return {
181 | dictionary: state.dictionary,
182 | detail: state.teacher,
183 | userState: state.login,
184 | }
185 | };
186 | export default connect(mapStateToProps)(TeacherDetail)
--------------------------------------------------------------------------------
/src/utils/TreeToo.js:
--------------------------------------------------------------------------------
1 | import { varEmpty, varNotEmpty, isArray, keyEmpty } from './util';
2 |
3 |
4 | const ID_ALL = "-1";
5 | const NODE_ALL = { id: ID_ALL, code: "all", display: "全部" };
6 | const NODE_ALL_LEVEL1 = { id: ID_ALL, code: "all", display: "年级" };
7 | const NODE_ALL_LEVEL2 = { id: ID_ALL, code: "all", display: "学科" };
8 |
9 | const _findItemByProperty = (items, propKey, value) => {
10 | return items.filter(item => item[propKey] + '' === value + '')[0];
11 | }
12 |
13 |
14 | const getNodeAll = (level) => {
15 | let nodeAll = NODE_ALL;
16 | if (level === 0)
17 | nodeAll = NODE_ALL_LEVEL1;
18 | else if (level === 1)
19 | nodeAll = NODE_ALL_LEVEL2;
20 | return nodeAll;
21 | }
22 |
23 | const findTreeNodeFromPath = (treeRootNodes, selectedIdsPath, level) => {
24 | let currentLevel = 0;
25 | let siblings = treeRootNodes;
26 | let currentNode = {};
27 | while (currentLevel <= level) {
28 | if (selectedIdsPath[currentLevel] + '' === ID_ALL)
29 | return getNodeAll(level);
30 | currentNode = _findItemByProperty(siblings, "id", selectedIdsPath[currentLevel]);
31 | siblings = currentNode.children;
32 | currentLevel++;
33 | }
34 | return currentNode;
35 | }
36 |
37 | const getValidTreeIdFromPath = (pathArray) => {
38 | if (varEmpty(pathArray))
39 | return '';
40 |
41 | if (pathArray[pathArray.length - 1] !== ID_ALL)
42 | return pathArray[pathArray.length - 1];
43 |
44 | if (pathArray.length === 1)
45 | return pathArray[0];
46 |
47 | return pathArray[pathArray.length - 2];
48 | }
49 |
50 | const getValidTreeIdFromPathStr = (pathArrayStr) => {
51 | return getValidTreeIdFromPathStr(pathArrayStr.split(','));
52 | }
53 |
54 | const getPathNameById = (id, node, currentPathName) => {
55 | if (!node || !node.id || !node.display) {
56 | return;
57 | }
58 |
59 | let newPathName = currentPathName + "-" + node.display;
60 | if (varEmpty(currentPathName))
61 | newPathName = node.display;
62 |
63 | if (node.id.toString() === id) {
64 | return newPathName;
65 | } else {
66 | let childNodes = node.children;
67 | if (childNodes && isArray(childNodes)) {
68 | for (let i = 0; i < childNodes.length; i++) {
69 | let cNode = childNodes[i];
70 | let name = getPathNameById(id, cNode, newPathName);
71 | if (name !== "") {
72 | return name;
73 | }
74 | }
75 | }
76 | }
77 | return "";
78 | }
79 |
80 | const findNodePathById = (nodePath, id) => {
81 | if (varEmpty(nodePath)) {
82 | return false;
83 | }
84 | let node = nodePath[nodePath.length -1];
85 |
86 | if (node.id === id) {
87 | return true;
88 | } else if (varEmpty(node.children) || !isArray(node.children)) {
89 | return false;
90 | } else {
91 | const length = node.children.length;
92 | for (let i=0; i < length; i ++) {
93 | nodePath.push(node.children[i]);
94 | const result = findNodePathById(nodePath, id);
95 | if (result) {
96 | return true;
97 | }
98 | nodePath.pop();
99 | }
100 | }
101 | }
102 |
103 | const getFatherNodePathByKtId = (nodes, id) => {
104 | if (varEmpty(nodes)) {
105 | return [];
106 | }
107 |
108 | for (let i=0; i < nodes.length; i++) {
109 | let nodePath = [nodes[i]];
110 | const result = findNodePathById(nodePath, id);
111 | if (result) {
112 | return nodePath;
113 | }
114 | }
115 | };
116 |
117 | //nodes,从context里面拿到知识树 this.context.dictionary.knowledgeTree
118 | //id, 从数据库等地方拿到 3-2(高中数学)
119 | //返回 高中-数学, 需要手动把-去掉,变为 高中数学.
120 | const getFullPathNameByKtId = (nodes, id) => {
121 | let pathName = "";
122 |
123 | if (varEmpty(nodes)) {
124 | return "";
125 | }
126 |
127 | for (let i = 0; i < nodes.length; i++) {
128 | let node = nodes[i];
129 | let name = getPathNameById(id, node, "");
130 | if (name !== "") {
131 | return name;
132 | }
133 | }
134 | return pathName;
135 | }
136 |
137 | const getNameByKtId = (nodes, id) => {
138 | let arr = getFullPathNameByKtId(nodes, id).split('-');
139 | if (varEmpty(arr))
140 | return '';
141 | return arr[arr.length - 1];
142 | }
143 |
144 | const getGradNameByKtId = (nodes, id) => {
145 | let arr = getFullPathNameByKtId(nodes, id).split('-');
146 | if (varEmpty(arr))
147 | return '';
148 | return arr[0];
149 | }
150 |
151 | const getSubjectByKtId = (nodes, id) => {
152 | let arr = getFullPathNameByKtId(nodes, id).split('-');
153 | if (varEmpty(arr))
154 | return '';
155 | return arr[1];
156 | }
157 |
158 | const treeIdsToNames = (tree, ids) => {
159 | return ids.split(',').map(id => getNameByKtId(tree, id)).join(',');
160 | }
161 |
162 | //将3-2(高中数学),的3拿出来,得到 0-3,
163 | //返回 ['0-3','3-2']
164 | const treeIdToTreeArray = (id) => {
165 | let grade = id.split('-')[0];
166 | let first = '0-' + grade;
167 | let result = [];
168 | result[0] = first;
169 | result[1] = id;
170 | return result;
171 | };
172 |
173 | //验证知识树,1 必须是array, 且长度长度必须 是2,不能包含-1
174 | const validateKtTree = (value) => {
175 | if(value instanceof Array && value.length === 2 ){
176 | value.forEach(obj => {
177 | if(obj === '-1'){
178 | return false;
179 | }
180 | });
181 | return true;
182 | }
183 | return false;
184 | };
185 |
186 | const getGradeSubjectNodes = (knowledgeTree, selectedIdsPath) => {
187 | const level_0 = 0;
188 | const level_1 = 1;
189 | let selectedGradeNode = knowledgeTree[0];
190 | let selectedSubjectNode = null;
191 |
192 | if (selectedIdsPath.length > 0) {
193 | let level0Node = findTreeNodeFromPath(knowledgeTree, selectedIdsPath, level_0);
194 | if (!varEmpty(level0Node.children)) {
195 | selectedGradeNode = level0Node;
196 | if (selectedIdsPath.length > 1) {
197 | selectedSubjectNode = findTreeNodeFromPath(knowledgeTree, selectedIdsPath, level_1);
198 | }
199 | else {
200 | selectedSubjectNode = level0Node.children[0];
201 | }
202 | }
203 | }
204 | return [selectedGradeNode, selectedSubjectNode];
205 | }
206 |
207 | const getRecordTreeGrad = (knowledgeTree, record) => {
208 | if (keyEmpty(record, "knowledgeTreeIds"))
209 | return '';
210 |
211 | let arr = record.knowledgeTreeIds.split(',');
212 | if (varEmpty(arr))
213 | return '';
214 | const validNames = arr.map(id => (getGradNameByKtId(knowledgeTree, id))).filter(name => varNotEmpty(name));
215 | // return validNames.join(',');
216 | return validNames[0];
217 | };
218 |
219 | const getRecordTreeSubject = (knowledgeTree, record) => {
220 | if (keyEmpty(record, "knowledgeTreeIds"))
221 | return '';
222 |
223 | let arr = record.knowledgeTreeIds.split(',');
224 | if (varEmpty(arr))
225 | return '';
226 | const validNames = arr.map(id => (getSubjectByKtId(knowledgeTree, id))).filter(name => varNotEmpty(name));
227 | // return validNames.join(',');
228 | return validNames[0];
229 | };
230 |
231 | const getRecordTreeNames = (knowledgeTree, record) => {
232 | if (keyEmpty(record, "knowledgeTreeIds"))
233 | return '';
234 |
235 | let arr = record.knowledgeTreeIds.split(',');
236 | if (varEmpty(arr))
237 | return '';
238 | const validNames = arr.map(id => (getNameByKtId(knowledgeTree, id))).filter(name => varNotEmpty(name));
239 | return validNames.join(', ');
240 | }
241 |
242 | const mapSubjectIdToName = (subjectList, subjectId) => {
243 | let arr = subjectList.filter(tempSubject => tempSubject.id + '' === subjectId + '');
244 | if (arr.length === 0)
245 | return '';
246 | return arr[0].display;
247 | }
248 |
249 | const mapGradeIdToName = (knowledgeTree, gradeId) => {
250 | let arr = knowledgeTree.filter(tempGrade => tempGrade.id.split('-')[1] + '' === gradeId + '');
251 | if (arr.length === 0)
252 | return '';
253 | return arr[0].display;
254 | }
255 |
256 | const getKnowledgeTreePath = (knowledgeTree, knowledgeTreeId) => {
257 | if (knowledgeTreeId === ID_ALL) {
258 | return [ID_ALL];
259 | }
260 |
261 | let fullPath = [];
262 | _getPath(knowledgeTree, knowledgeTreeId, fullPath);
263 | return fullPath;
264 | }
265 |
266 | const _getPath = (knowledgeTree, targetId, fullPath) => {
267 | if (knowledgeTree.length > 0) {
268 | for (let i in knowledgeTree) {
269 | if (knowledgeTree[i].children) {
270 | if (knowledgeTree[i].id.toString() === targetId) {
271 | fullPath.unshift(knowledgeTree[i].id);
272 | fullPath.push("-1");
273 | break;
274 | } else {
275 | _getPath(knowledgeTree[i].children, targetId, fullPath);
276 | if (fullPath.length > 0) {
277 | fullPath.unshift(knowledgeTree[i].id);
278 | break;
279 | }
280 | }
281 | }
282 | else {
283 | if (knowledgeTree[i].id.toString() === targetId) {
284 | fullPath.unshift(knowledgeTree[i].id);
285 | break;
286 | }
287 | }
288 | }
289 | }
290 | }
291 |
292 | export {
293 | findTreeNodeFromPath, ID_ALL, NODE_ALL, getNodeAll, getValidTreeIdFromPath, getValidTreeIdFromPathStr,
294 | getFullPathNameByKtId, getNameByKtId, treeIdsToNames, treeIdToTreeArray,validateKtTree,
295 | getGradeSubjectNodes, getRecordTreeNames, getRecordTreeSubject, getRecordTreeGrad, mapSubjectIdToName,
296 | mapGradeIdToName, getKnowledgeTreePath, getFatherNodePathByKtId, getGradNameByKtId, getSubjectByKtId
297 | };
298 |
--------------------------------------------------------------------------------
/src/utils/tableColumnsDef.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {Popconfirm, Icon} from 'antd'
3 | import {getRecordTreeGrad, getRecordTreeNames, getRecordTreeSubject,
4 | mapGradeIdToName, mapSubjectIdToName, getGradNameByKtId, getSubjectByKtId} from '../utils/TreeToo'
5 | import {EXERCISE_TYPE, Biz_Target_Status, GENDER_MALE, GENDER_FEMALE} from './constants'
6 | import {isVerified} from './util'
7 |
8 |
9 | const getCourseColumns = (knowledgeTree, editRecord, deleteRecord) => {
10 | return [
11 | {
12 | title: '名称',
13 | dataIndex: 'title',
14 | }, {
15 | title: '发布教师',
16 | width: 100,
17 | dataIndex: 'teacherName'
18 | },{
19 | title: '年级',
20 | width: 100,
21 | render: (text, record, index) => {
22 | return getRecordTreeGrad(knowledgeTree, record);
23 | }
24 | }, {
25 | title: '科目',
26 | width: 100,
27 | render: (text, record) => {
28 | return getRecordTreeSubject(knowledgeTree, record);
29 | }
30 | }, {
31 | title: '知识树',
32 | width: 250,
33 | render: (text, record) => {
34 | return getRecordTreeNames(knowledgeTree, record);
35 | }
36 | }, {
37 | title: '审核状态',
38 | width: 100,
39 | render: (text, record) => {
40 | if(isVerified(record.bizTargetStatus)) {
41 | return record.bizTargetStatus === Biz_Target_Status.RELEASED? '审核已通过' : '审核未通过';
42 | } else {
43 | return '未审核';
44 | }
45 | }
46 | }, {
47 | title: 'Action',
48 | width: 80,
49 | render: (text, record, index) => {
50 | const deleteMsg = 'Are you sure delete this record';
51 | return (
52 |
53 |
editRecord(index)}>
54 |
55 | {/*
deleteRecord(index)}>*/}
57 | {/**/}
58 | {/**/}
59 |
60 | )
61 | }
62 | }
63 | ];
64 | };
65 |
66 | const getExamColumns = (knowledgeTree, editRecord, deleteRecord) => {
67 | return [
68 | {
69 | title: '题干',
70 | dataIndex: 'content',
71 | },{
72 | title: '类型',
73 | width: 100,
74 | render: (text, record) => {
75 | return EXERCISE_TYPE[record.type] && EXERCISE_TYPE[record.type].name
76 | }
77 | }, {
78 | title: '年级',
79 | width: 100,
80 | render: (text, record) => {
81 | return getRecordTreeGrad(knowledgeTree, record);
82 | }
83 | }, {
84 | title: '科目',
85 | width: 100,
86 | render: (text, record) => {
87 | return getRecordTreeSubject(knowledgeTree, record);
88 | }
89 | }, {
90 | title: '知识树',
91 | width: 250,
92 | render: (text, record) => {
93 | return getRecordTreeNames(knowledgeTree, record);
94 | }
95 | }, {
96 | title: '审核状态',
97 | width: 100,
98 | render: (text, record) => {
99 | if(isVerified(record.bizTargetStatus)) {
100 | return record.bizTargetStatus === Biz_Target_Status.RELEASED? '审核已通过' : '审核未通过';
101 | } else {
102 | return '未审核';
103 | }
104 | }
105 | }, {
106 | title: 'Action',
107 | width: 80,
108 | render: (text, record,index) => {
109 | const deleteMsg = 'Are you sure delete this record';
110 | return (
111 |
112 |
editRecord(index)}>
113 |
114 | {/*
deleteRecord(index)}>
116 |
117 | */}
118 |
119 | )
120 | }
121 | }
122 | ]
123 | };
124 |
125 | const getTeacherColumns = (dictionary, editRecord, deleteRecord) => {
126 | return [
127 | {
128 | title: '姓名',
129 | dataIndex: 'userNameFull',
130 | },{
131 | title: '性别',
132 | render: (text, record) => {
133 | if (record.genderId === GENDER_MALE) {
134 | return '男';
135 | } else if (record.genderId === GENDER_FEMALE) {
136 | return '女';
137 | }
138 | return '';
139 | }
140 | }, {
141 | title: '年级',
142 | render: (text, record) => {
143 | return mapGradeIdToName(dictionary.knowledgeTree, record.gradeId);
144 | }
145 | }, {
146 | title: '科目',
147 | render: (text, record) => {
148 | return mapSubjectIdToName(dictionary.subjectList, record.subjectId);
149 | }
150 | }, {
151 | title: '审核状态',
152 | render: (text, record) => {
153 | if(record.verified) {
154 | return '已审核';
155 | } else {
156 | return '未审核';
157 | }
158 | }
159 | }, {
160 | title: 'Action',
161 | width: 80,
162 | render: (text, record, index) => {
163 | const deleteMsg = 'Are you sure delete this record';
164 | return (
165 |
166 |
editRecord(index)}>
167 |
168 | {/*
deleteRecord(index)}>
170 |
171 | */}
172 |
173 | )
174 | }
175 | }
176 | ];
177 | };
178 |
179 | const getLiveColumns = (knowledgeTree, editRecord, deleteRecord) => {
180 | return [
181 | {
182 | title: '标题',
183 | dataIndex: 'title',
184 | },{
185 | title: '发布教师',
186 | width: 100,
187 | dataIndex: 'teacherName',
188 | }, {
189 | title: '年级',
190 | width: 100,
191 | render: (text, record, index) => {
192 | // return getRecordTreeGrad(knowledgeTree, record);
193 | return getGradNameByKtId(knowledgeTree, record.knowledgeTreeId[0]);
194 | }
195 | }, {
196 | title: '科目',
197 | width: 100,
198 | render: (text, record) => {
199 | // return getRecordTreeSubject(knowledgeTree, record);
200 | return getSubjectByKtId(knowledgeTree, record.knowledgeTreeId[0]);
201 | }
202 | }, {
203 | title: '审核状态',
204 | width: 100,
205 | render: (text, record) => {
206 | if(isVerified(record.auditStatus)) {
207 | return record.auditStatus === Biz_Target_Status.RELEASED? '审核已通过' : '审核未通过';
208 | } else {
209 | return '未审核';
210 | }
211 | }
212 | }, {
213 | title: 'Action',
214 | width: 80,
215 | render: (text, record, index) => {
216 | const deleteMsg = 'Are you sure delete this record';
217 | return (
218 |
219 |
editRecord(index)}>
220 |
221 | {/*
deleteRecord(index)}>
223 |
224 | */}
225 |
226 | )
227 | }
228 | }
229 | ]
230 | };
231 |
232 | const getSchoolColumns = (editRecord, deleteRecord) => {
233 | return [
234 | {
235 | title: '名称',
236 | dataIndex: 'name',
237 | }, {
238 | title: '审核状态',
239 | width: 150,
240 | render: (text, record) => {
241 | if(record.verified) {
242 | return '已审核';
243 | } else {
244 | return '未审核';
245 | }
246 | }
247 | }, {
248 | title: 'Action',
249 | width: 80,
250 | render: (text, record, index) => {
251 | const deleteMsg = 'Are you sure delete this record';
252 | return (
253 |
254 |
editRecord(index)}>
255 |
256 | {/*
deleteRecord(index)}>
258 |
259 | */}
260 |
261 | )
262 | }
263 | }
264 | ];
265 | };
266 |
267 |
268 | export {getCourseColumns, getExamColumns, getTeacherColumns, getLiveColumns, getSchoolColumns}
--------------------------------------------------------------------------------
/src/containers/exerciseDetail.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import KnowledgeTreeItem from '../components/knowledgeTreeItem'
5 | import {ID_ALL} from '../utils/TreeToo'
6 | import {varEmpty, varNotEmpty, isVerified} from '../utils/util'
7 | import {doAuditResource, doUpdateKnowledgeTree} from '../actions/auditResource.action'
8 | import {EXERCISE_TYPE, FILL_EXERCISE, SUBJECT_EXERCISE, Biz_Target_Type, Biz_Target_Status} from '../utils/constants'
9 | import ExerciseContent from '../components/ExerciseConent'
10 | import {Button, Input, message, Radio, Rate, Tooltip, Icon} from 'antd'
11 | import '../App.css'
12 | const RadioGroup = Radio.Group;
13 |
14 |
15 | class ExerciseDetail extends Component {
16 | constructor(props) {
17 | super(props);
18 | const {exercise} = this.props.detail;
19 | this.state = {
20 | passed: false,
21 | knowledgeTreeIds: varEmpty(exercise.knowledgeTreeIds) ? ID_ALL : '' + exercise.knowledgeTreeIds,
22 | }
23 | }
24 |
25 | onCheckStatusChange(e) {
26 | this.setState({passed: e.target.value})
27 | }
28 |
29 | closePage() {
30 | browserHistory.goBack();
31 | }
32 |
33 | auditFailed() {
34 | message.error('审批失败!');
35 | }
36 |
37 | auditExercise() {
38 | const textArea = document.getElementById('comment');
39 | if (!this.state.passed) {
40 | if (textArea.value === '') {
41 | message.warning('请输入未通过原因');
42 | return;
43 | }
44 | }
45 |
46 | const {detail, userState} = this.props;
47 | const requestInfo = {
48 | auditComment: textArea.value,
49 | auditPassed: this.state.passed,
50 | bizTargetType: Biz_Target_Type.EXERCISE,
51 | targetId: detail.exercise.exerciseItemId,
52 | userToken: userState.userInfo.userToken
53 | };
54 |
55 | doAuditResource(requestInfo, this.closePage, this.auditFailed);
56 | }
57 |
58 | updateKnowledgeTree(updatedKnowledgeIds) {
59 | const {detail, userState} = this.props;
60 | doUpdateKnowledgeTree(
61 | {
62 | "bizTargetType": Biz_Target_Type.EXERCISE,
63 | "targetId": detail.exercise.exerciseItemId,
64 | "userToken": userState.userInfo.userToken,
65 | "knowledgeTreeIds": updatedKnowledgeIds
66 | },
67 | this.auditExercise.bind(this),
68 | (msg) => {message.error(msg)}
69 | )
70 | }
71 | onConfirmBtnClick() {
72 | if (this.isVerified) {
73 | this.closePage();
74 | return;
75 | }
76 |
77 | const {detail} = this.props;
78 | const validTrees = this.knowledgeTreeIdList.filter(tree => tree !== ID_ALL);
79 | const currentKnowledgeIds = validTrees.join(',');
80 |
81 | if (detail.exercise.knowledgeTreeIds !== currentKnowledgeIds) {
82 | this.updateKnowledgeTree(currentKnowledgeIds);
83 | } else {
84 | this.auditExercise();
85 | }
86 | }
87 |
88 | addKnowledgeTree() {
89 | this.knowledgeTreeIdList = this.knowledgeTreeIdList.concat([ID_ALL]);
90 |
91 | this.setState({knowledgeTreeIds: this.knowledgeTreeIdList.join(',')});
92 | }
93 |
94 | getStage(level) {
95 | if (level === '1') {
96 | return '新课';
97 | } else if (level === '2') {
98 | return '阶段性复习';
99 |
100 | } else if (level === '3') {
101 | return '总复习';
102 | }
103 | return '未知阶段';
104 | }
105 |
106 | componentWillMount() {
107 | this.knowledgeTreeIdList = this.state.knowledgeTreeIds.split(',');
108 |
109 | const {exercise} = this.props.detail;
110 | this.isVerified = isVerified(exercise.bizTargetStatus);
111 | }
112 |
113 | render() {
114 | const {exercise} = this.props.detail;
115 |
116 | return (
117 |
118 |
119 |
120 |
121 |
{EXERCISE_TYPE[exercise.type] && EXERCISE_TYPE[exercise.type].name}
122 |
123 |
124 |
125 |
126 |
127 | {varNotEmpty(exercise.content)
128 | ?
129 | : ''
130 | }
131 |
132 |
133 | { exercise.type !== FILL_EXERCISE.value && exercise.type !== SUBJECT_EXERCISE.value && varNotEmpty(exercise.choiceItems)
134 | ? exercise.choiceItems.map((item, index) => (
135 |
136 |
137 | {varNotEmpty(item.content)
138 | ?
139 | : ''
140 | }
141 |
142 | ))
143 | : ''
144 | }
145 |
146 | { varNotEmpty(exercise.answers)
147 | ? exercise.answers.map((answer, index) => (
148 |
149 |
150 | {varNotEmpty(answer.rightAnswer)
151 | ?
{answer.title}
152 | : ''
153 | }
154 |
155 | ))
156 | : ''
157 | }
158 |
159 |
160 |
161 | {varNotEmpty(exercise.explanation)
162 | ?
163 | : ''
164 | }
165 |
166 |
167 |
168 |
169 | {!this.isVerified ?
170 |
171 |
172 |
173 |
174 | this.addKnowledgeTree()}>
175 |
176 |
177 |
178 |
179 |
180 | : ''
181 | }
182 |
183 | { varNotEmpty(exercise.difficultyDegree)
184 | ? exercise.difficultyDegree.map((item, index) => (
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 | ))
194 | : ''
195 | }
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 | {!this.isVerified ?
205 |
206 | this.onCheckStatusChange(e)} value={this.state.passed}>
207 | 通过
208 | 否决
209 |
210 |
211 | :
214 | }
215 |
216 |
217 | {!this.isVerified ?
218 |
219 |
220 |
221 |
222 |
223 | : ''
224 | }
225 |
226 | {this.isVerified
227 | ?
228 |
229 |
230 | :
231 |
232 |
233 |
234 | }
235 |
236 | )
237 | }
238 | }
239 | const mapStateToProps = (state) => {
240 | return {
241 | dictionary: state.dictionary,
242 | userState: state.login,
243 | detail: state.exam,
244 | }
245 | };
246 | export default connect(mapStateToProps)(ExerciseDetail)
247 |
--------------------------------------------------------------------------------
/src/containers/liveDetail.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import KnowledgeTreeItem from '../components/knowledgeTreeItem'
5 | import {ID_ALL} from '../utils/TreeToo'
6 | import {varEmpty, isVerified} from '../utils/util'
7 | import {doCreateLivePlayer} from '../actions/livePlayer.action'
8 | import {doAuditResource, doUpdateKnowledgeTree} from '../actions/auditResource.action'
9 | import {Biz_Target_Type, Biz_Target_Status} from '../utils/constants'
10 | import {Button, Icon, Input, message, Radio, Tooltip} from 'antd'
11 | import '../App.css'
12 | const RadioGroup = Radio.Group;
13 |
14 | class LiveDetail extends Component {
15 | constructor(props) {
16 | super(props);
17 | const {live} = this.props.detail;
18 | this.state = {
19 | startPlay: false,
20 | passed: false,
21 | knowledgeTreeIds: varEmpty(live.knowledgeTreeIds) ? ID_ALL : '' + live.knowledgeTreeIds,
22 | };
23 | }
24 |
25 | getFileInfo(fileItem) {
26 | if (!fileItem) {
27 | return {};
28 | }
29 | const pathNames = fileItem.fileAddress.split('/');
30 | return {
31 | fileName: pathNames[pathNames.length - 1],
32 | fileUrl: fileItem.fileAddress,
33 | fileType: fileItem.fileType
34 | }
35 | }
36 |
37 | getLiveItemByType(type) {
38 | const {live} = this.props.detail;
39 | // const files = live.courseItems.filter(item => item.itemType === type);
40 | const files = [];
41 | return files.length && files[0];
42 | }
43 |
44 | onCheckStatusChange(e) {
45 | this.setState({passed: e.target.value})
46 | }
47 |
48 | onLivePlayerClick() {
49 | this.setState({startPlay: true});
50 | }
51 |
52 | closePage() {
53 | browserHistory.goBack();
54 | }
55 |
56 | auditFailed() {
57 | message.error('审批失败!');
58 | }
59 |
60 | auditLive() {
61 | const textArea = document.getElementById('comment');
62 | if (!this.state.passed) {
63 | if (textArea.value === '') {
64 | message.warning('请输入未通过原因');
65 | return;
66 | }
67 | }
68 |
69 | const {detail, userState} = this.props;
70 | const requestInfo = {
71 | auditComment: textArea.value,
72 | auditPassed: this.state.passed,
73 | bizTargetType: Biz_Target_Type.LIVE,
74 | targetId: detail.live.liveId,
75 | userToken: userState.userInfo.userToken
76 | };
77 |
78 | doAuditResource(requestInfo, this.closePage, this.auditFailed);
79 | }
80 |
81 | updateKnowledgeTree(updatedKnowledgeIds) {
82 | const {detail, userState} = this.props;
83 | doUpdateKnowledgeTree(
84 | {
85 | "bizTargetType": Biz_Target_Type.LIVE,
86 | "targetId": detail.live.liveId,
87 | "userToken": userState.userInfo.userToken,
88 | "knowledgeTreeIds": updatedKnowledgeIds
89 | },
90 | this.auditLive.bind(this),
91 | (msg) => {message.error(msg)}
92 | )
93 | }
94 |
95 | onConfirmBtnClick() {
96 | if (this.isVerified) {
97 | this.closePage();
98 | return;
99 | }
100 |
101 | const {detail} = this.props;
102 | const validTrees = this.knowledgeTreeIdList.filter(tree => tree !== ID_ALL);
103 | const currentKnowledgeIds = validTrees.join(',');
104 |
105 | if (detail.live.knowledgeTreeIds !== currentKnowledgeIds) {
106 | this.updateKnowledgeTree(currentKnowledgeIds);
107 | } else {
108 | this.auditLive();
109 | }
110 | }
111 |
112 | addKnowledgeTree() {
113 | this.knowledgeTreeIdList = this.knowledgeTreeIdList.concat([ID_ALL]);
114 |
115 | this.setState({knowledgeTreeIds: this.knowledgeTreeIdList.join(',')});
116 | }
117 |
118 | componentWillMount() {
119 | const {live} = this.props.detail;
120 |
121 | this.courseCase = this.getFileInfo(this.getLiveItemByType(2));
122 | this.liveVideo = this.getFileInfo(this.getLiveItemByType(1));
123 |
124 | if (this.state.knowledgeTreeIds === '-1') {
125 | this.knowledgeTreeIdList = live.knowledgeTreeId ? live.knowledgeTreeId : ['-1'];
126 | } else {
127 | this.knowledgeTreeIdList = this.state.knowledgeTreeIds.split(',');
128 | }
129 |
130 | this.isVerified = isVerified(live.auditStatus);
131 | }
132 |
133 | componentDidUpdate() {
134 | if (this.state.startPlay) {
135 | const {dispatch} = this.props;
136 | doCreateLivePlayer({
137 | liveBox: 'video-box',
138 | playerType: 'video/mp4',
139 | pullStreamUrl: this.liveVideo.fileUrl,
140 | })(dispatch);
141 | }
142 | }
143 |
144 | componentWillUnmount() {
145 | if (this.props.liveObj.livePlayer) {
146 | this.props.liveObj.livePlayer.release();
147 | }
148 | }
149 |
150 | LivePlayerRender() {
151 | if (this.state.startPlay) {
152 | return (
153 |
154 |
155 |
156 |
157 |
158 |
159 | )
160 | } else {
161 | return (
162 |
163 |
164 |
166 |
167 | )
168 | }
169 | }
170 |
171 | render() {
172 | const {live} = this.props.detail;
173 |
174 | return (
175 |
176 |
177 |
178 |
179 |

180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 | {!this.isVerified ?
212 |
213 |
214 |
215 |
216 | this.addKnowledgeTree()}>
217 |
218 |
219 |
220 |
221 |
222 |
223 | : ''
224 | }
225 |
226 |
233 |
234 | {this.LivePlayerRender()}
235 |
236 |
237 |
238 |
239 | {!this.isVerified ?
240 |
241 | this.onCheckStatusChange(e)} value={this.state.passed}>
242 | 通过
243 | 否决
244 |
245 |
246 | :
249 | }
250 |
251 |
252 | {!this.isVerified ?
253 |
254 |
255 |
256 |
257 |
258 | : ''
259 | }
260 |
261 | {this.isVerified
262 | ?
263 |
264 |
265 | :
266 |
267 |
268 |
269 | }
270 |
271 | )
272 | }
273 | }
274 | const mapStateToProps = (state) => {
275 | return {
276 | dictionary: state.dictionary,
277 | userState: state.login,
278 | detail: state.live,
279 | liveObj: state.livePlayer,
280 | }
281 | };
282 | export default connect(mapStateToProps)(LiveDetail)
--------------------------------------------------------------------------------
/src/containers/courseDetail.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react'
2 | import {connect} from 'react-redux'
3 | import {browserHistory} from 'react-router'
4 | import KnowledgeTreeItem from '../components/knowledgeTreeItem'
5 | import {ID_ALL} from '../utils/TreeToo'
6 | import {varEmpty, isVerified, mapTagIdsToNames} from '../utils/util'
7 | import {Button, Icon, Input, message, Radio, Tooltip} from 'antd'
8 | import {doCreateLivePlayer} from '../actions/livePlayer.action'
9 | import {doAuditResource, doUpdateKnowledgeTree} from '../actions/auditResource.action'
10 | import {Biz_Target_Type, CourseItemType, Biz_Target_Status} from '../utils/constants'
11 | import '../App.css'
12 | const RadioGroup = Radio.Group;
13 |
14 | class CourseDetail extends Component {
15 | constructor(props) {
16 | super(props);
17 | const {course} = this.props.detail;
18 | this.state = {
19 | startPlay: false,
20 | knowledgeTreePaths: [],
21 | knowledgeTreeIds: varEmpty(course.knowledgeTreeIds) ? ID_ALL : '' + course.knowledgeTreeIds,
22 | passed: false,
23 | };
24 | }
25 |
26 | getFileInfo(fileItem) {
27 | if (!fileItem) {
28 | return {};
29 | }
30 | const pathNames = fileItem.fileAddress.split('/');
31 | return {
32 | fileName: pathNames[pathNames.length - 1],
33 | fileUrl: fileItem.fileAddress,
34 | fileType: fileItem.fileType
35 | }
36 | }
37 |
38 | getCourseItemByType(type) {
39 | const {course} = this.props.detail;
40 | const files = course.courseItems.filter(item => item.itemTypeCode === type);
41 | return files.length && files[0];
42 | }
43 |
44 | onCheckStatusChange(e) {
45 | this.setState({passed: e.target.value})
46 | }
47 |
48 | onLivePlayerClick() {
49 | this.setState({startPlay: true});
50 | }
51 |
52 | closePage() {
53 | browserHistory.goBack();
54 | }
55 |
56 | auditFailed() {
57 | message.error('审批失败!');
58 | }
59 |
60 | auditCourse() {
61 | const textArea = document.getElementById('comment');
62 | if (!this.state.passed) {
63 | if (textArea.value === '') {
64 | message.warning('请输入未通过原因');
65 | return;
66 | }
67 | }
68 |
69 | const {detail, userState} = this.props;
70 | const requestInfo = {
71 | auditComment: textArea.value,
72 | auditPassed: this.state.passed,
73 | bizTargetType: Biz_Target_Type.COURSE,
74 | targetId: detail.course.id,
75 | userToken: userState.userInfo.userToken
76 | };
77 |
78 | doAuditResource(requestInfo, this.closePage, this.auditFailed);
79 | }
80 |
81 | updateKnowledgeTree(updatedKnowledgeIds) {
82 | const {detail, userState} = this.props;
83 | doUpdateKnowledgeTree(
84 | {
85 | "bizTargetType": Biz_Target_Type.COURSE,
86 | "targetId": detail.course.id,
87 | "userToken": userState.userInfo.userToken,
88 | "knowledgeTreeIds": updatedKnowledgeIds
89 | },
90 | this.auditCourse.bind(this),
91 | (msg) => {message.error(msg)}
92 | )
93 | }
94 |
95 | onConfirmBtnClick() {
96 | if (this.isVerified) {
97 | this.closePage();
98 | return;
99 | }
100 |
101 | const {detail} = this.props;
102 | const validTrees = this.knowledgeTreeIdList.filter(tree => tree !== ID_ALL);
103 | const currentKnowledgeIds = validTrees.join(',');
104 |
105 | if (detail.course.knowledgeTreeIds !== currentKnowledgeIds) {
106 | this.updateKnowledgeTree(currentKnowledgeIds);
107 | } else {
108 | this.auditCourse();
109 | }
110 | }
111 |
112 | addKnowledgeTree() {
113 | this.knowledgeTreeIdList = this.knowledgeTreeIdList.concat([ID_ALL]);
114 |
115 | this.setState({knowledgeTreeIds: this.knowledgeTreeIdList.join(',')});
116 | }
117 |
118 | componentWillMount() {
119 | this.courseCase = this.getFileInfo(this.getCourseItemByType(CourseItemType.COURSE_CASE));
120 | this.liveVideo = this.getFileInfo(this.getCourseItemByType(CourseItemType.VIDEO));
121 | this.learningCase = this.getFileInfo(this.getCourseItemByType(CourseItemType.LEARNING_CASE));
122 |
123 | this.knowledgeTreeIdList = this.state.knowledgeTreeIds.split(',');
124 |
125 | const {course} = this.props.detail;
126 | this.isVerified = isVerified(course.bizTargetStatus);
127 | }
128 |
129 | componentDidUpdate() {
130 | if (this.state.startPlay) {
131 | const {dispatch} = this.props;
132 | doCreateLivePlayer({
133 | liveBox: 'video-box',
134 | playerType: 'video/mp4',
135 | pullStreamUrl: this.liveVideo.fileUrl,
136 | })(dispatch);
137 | }
138 | }
139 |
140 | componentWillUnmount() {
141 | if (this.props.liveObj.livePlayer) {
142 | this.props.liveObj.livePlayer.release();
143 | }
144 | }
145 |
146 | LivePlayerRender() {
147 | if (this.state.startPlay) {
148 | return (
149 |
150 |
151 |
152 |
153 |
154 |
155 | )
156 | } else {
157 | return (
158 |
159 |
160 |
163 |
164 | )
165 | }
166 | }
167 |
168 | render() {
169 | const {course} = this.props.detail;
170 | const {dictionary} = this.props.dictionary;
171 |
172 | return (
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
193 |
194 |
195 |
196 |
197 | {!this.isVerified ?
198 |
199 |
200 |
201 |
202 | this.addKnowledgeTree()}>
203 |
204 |
205 |
206 |
207 |
208 | : ''
209 | }
210 |
211 |
212 |
213 |
214 |
215 |
220 |
221 |
222 | {this.LivePlayerRender()}
223 |
224 |
232 |
233 |
234 |
235 | {!this.isVerified ?
236 |
237 | this.onCheckStatusChange(e)} value={this.state.passed}>
238 | 通过
239 | 否决
240 |
241 |
242 | :
245 | }
246 |
247 |
248 |
249 | {!this.isVerified ?
250 |
251 |
252 |
253 |
254 |
255 | : ''
256 | }
257 |
258 | {this.isVerified
259 | ?
260 |
261 |
262 | :
263 |
264 |
265 |
266 | }
267 |
268 | )
269 | }
270 | }
271 | const mapStateToProps = (state) => {
272 | return {
273 | dictionary: state.dictionary,
274 | userState: state.login,
275 | detail: state.course,
276 | liveObj: state.livePlayer,
277 | }
278 | };
279 |
280 | export default connect(mapStateToProps)(CourseDetail)
--------------------------------------------------------------------------------
/public/nep.min.css:
--------------------------------------------------------------------------------
1 | .video-js .vjs-audio-button,.video-js .vjs-big-play-button,.video-js .vjs-captions-button,.video-js .vjs-chapters-button,.video-js .vjs-control.vjs-close-button,.video-js .vjs-descriptions-button,.video-js .vjs-fullscreen-control,.video-js .vjs-mouse-display,.video-js .vjs-mute-control,.video-js .vjs-mute-control.vjs-vol-0,.video-js .vjs-mute-control.vjs-vol-1,.video-js .vjs-mute-control.vjs-vol-2,.video-js .vjs-play-control,.video-js .vjs-play-control.vjs-playing,.video-js .vjs-play-progress,.video-js .vjs-subtitles-button,.video-js .vjs-volume-level,.video-js .vjs-volume-menu-button,.video-js .vjs-volume-menu-button.vjs-vol-0,.video-js .vjs-volume-menu-button.vjs-vol-1,.video-js .vjs-volume-menu-button.vjs-vol-2,.video-js.vjs-fullscreen .vjs-fullscreen-control,.vjs-icon-audio,.vjs-icon-audio-description,.vjs-icon-cancel,.vjs-icon-captions,.vjs-icon-chapters,.vjs-icon-circle,.vjs-icon-circle-inner-circle,.vjs-icon-circle-outline,.vjs-icon-cog,.vjs-icon-facebook,.vjs-icon-fullscreen-enter,.vjs-icon-fullscreen-exit,.vjs-icon-gplus,.vjs-icon-hd,.vjs-icon-linkedin,.vjs-icon-pause,.vjs-icon-pinterest,.vjs-icon-play,.vjs-icon-play-circle,.vjs-icon-replay,.vjs-icon-spinner,.vjs-icon-square,.vjs-icon-subtitles,.vjs-icon-tumblr,.vjs-icon-twitter,.vjs-icon-volume-high,.vjs-icon-volume-low,.vjs-icon-volume-mid,.vjs-icon-volume-mute{font-family:VideoJS;font-weight:400;font-style:normal}.video-js,.vjs-no-js{color:#fff;background-color:#000}.video-js .vjs-big-play-button:before,.video-js .vjs-control,.video-js .vjs-control:before,.vjs-menu li,.vjs-no-js{text-align:center}.video-js .vjs-big-play-button:before,.video-js .vjs-control:before,.video-js .vjs-modal-dialog,.vjs-modal-dialog .vjs-modal-dialog-content{position:absolute;top:0;left:0;width:100%;height:100%}@font-face{font-family:VideoJS;src:url(font/VideoJS.eot?#iefix) format("eot")}@font-face{font-family:VideoJS;src:url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAA54AAoAAAAAFmgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAAA9AAAAD4AAABWUZFeBWNtYXAAAAE0AAAAOgAAAUriMBC2Z2x5ZgAAAXAAAAouAAAPUFvx6AdoZWFkAAALoAAAACsAAAA2DIPpX2hoZWEAAAvMAAAAGAAAACQOogcgaG10eAAAC+QAAAAPAAAAfNkAAABsb2NhAAAL9AAAAEAAAABAMMg06m1heHAAAAw0AAAAHwAAACABMAB5bmFtZQAADFQAAAElAAACCtXH9aBwb3N0AAANfAAAAPwAAAGBZkSN43icY2BkZ2CcwMDKwMFSyPKMgYHhF4RmjmEIZzzHwMDEwMrMgBUEpLmmMDh8ZPwoxw7iLmSHCDOCCADvEAo+AAB4nGNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZgYGD7K/f8PUvCREUTzM0DVAwEjG8OIBwCPdwbVAAB4nI1Xe1CU1xX/zv1eLItLln0JwrIfC7sJGET2hRJ2N1GUoBJE8AESQEEhmBHjaB7UuBMTO4GMaSu7aY3RNlOdRPNqO2pqRmuTaSZtR6JJILUZk00a/4imjpmiecB303O/XUgMJOPufvd+99xzzz33nN855y4HHH7EfrGfIxwHRiANvF/sH71I9BzHszmpW+rGOQOXxXE6YhI4PoMT8zkT4cDFuf1cwMrZJI5cglM0HKVv0MaUFDgIFfg9mJJCG+kbKn1JkqBOVaFOkuhLpARq8fu0Nnc9/zdvfY9PxXW4PdH0C6N+PCejhorxFjAqRjgFRXSINEARbBGsoxcFK7IJmr4OycFJnInL59zIXwxui80fkGRbEHyosMWaATJKUfCskmwJQsAWANkmnIGOhlf514h7U8HNIv3owoHB0WMt0Eb3sx0guLi5pq/8Ny1q6969fKR9X9GBV6dPv6dp04K99SOwtmyPl47ApRa6n4ZpP1yjr5fn7MmYP/vXLUJs715UguklHBaHOZHZmG1N9FAIW2mf0MqWCIdo/8RZ1yGfxKUldDcGIbFA7ICO+vqOMSPTh/ZrSqgHi/bB/O8E8Mnzp+M+acxfpsTShBwej26TiGxBn7m4eEIO+Rueu6Hj+IFBnh88cAEUEQ//nVLx5C7kf+yIR47QEe+eMlhz9SqsGbe3hh2R03NGzoY6O42Kz8l7fB6fAk6LYnTyFo/FYyT6GGyNx2Jx2sdH4rA1Fo/HyCXaFyOp8dhYBCfJb2NIn1ImE6CYNGmgSTb52DawJR6jfXEmDU4xyTEmpgHHOIStoxfjSGdkbsK2w2jbdMQG4sgAstEONgURYCwGHhEhhscioQaAhhCf7McifEQc0l6+mxj9nI+gmSdiQ0Zbm7gZnIO7GSMEXG6UDAVocxAV8GcEXCKg1a02RcTtwANWRGIAyElor6n/+ZU2yOB3+T77Hb1MLqhn4KHVnQBjJnqe9QZSon6Kc5DxAD2vMdPL/BXSmQGwspa67z9wLUjdi9TN7QC7lyyBr9rpt7uXVC1CMpyjKRoXnGPHTuiaPLsNdc2dbAFQLAooPkXEh33FodHl4XpC6sPCIa0ftUIhHSYXVSu5iME+DIXsbZJ51BeidCgajcai43jU9nVzoSn2dPqcFvSoxSzJzgRKAx47WMRxOrIj3Wf0+hndxhJTiOkSEqxar3b3RKM9hY64oxBA64ieURLvCfpkDb8siBdUJ1bgT+urJ5PGfewQrmm5R5+0HmfyIPySD7OYkT0WxRePah8oEiyjlxIP74thVoRTURpmL6QhGuWS+QDjdANXjIM8SQa/1w128ODx0Qp4aLMNg9+JL3joUn8AMxW+aLNiuKjarn4uyyTdXjOzZTsh21uwldUvJoYza+zELALfu3p1L8/3krtyZ0Ag058J3hxHghvbGZn0dHZy6Mim/7Blre4lpHd1c28yVqRViO153F2oIWoXCIKbL4Z0cM1iaQn9mI5KuV2SzEvWXJDMNtkANpMdQoDDhIdD4A/YrP6Aye9ysxyE+uOEAcTDorgvVZJjcua043PnZ/PmdDqcbibZlXOOT8uSo7Kof0YUn9GL+Jo17ficymxiTofC6znUso0DhAxs1Fo+kF+d36vLmgZ8mk5cdGv2mwYj5k3Dm9m3LhJ1aVRNm6HrTbLgYAoWXDhDd/u4PGy5CT+xGMdiaBovewUCF/1BiWNljI9MLn7jeScpg+WyH6mfU62eVDql7hsrmvx1ezp/YldE2LhjbkiDnAn8tGy/MW3IXRMYJduvq9HpmIcKuFt+JCtgdGEGKAcF6UacVwIYbVPGfw/+YuNBS4cx/CUHcnyfc+wRDMtTr72mMSBjT/yn/GKSdeDWQUCH6Xoqq5R10RE60gV6erUL0iCti16d0hZjxut4QI/rEpgSh6WjnJXdBXRg1GKCucGJPtFqM27aD1tOqqKonsQ2KsFSSmEpmvRlsR+TcD9OFwrqXxIclL4sJTnGMSuG8KpkZvKdeVIOKDyWSyPLV16/p1QMPbP8NihwUzr47bdnXtwtjdCvqqpO0H+pOvIl3Pzv46e5CT/tQjklXCXXym1AaWY7bzHLkuDMc7ldKCvgxzLn8wYkJLBhEDyK7MT8bTbwbkxbfp+3mKAGsmTBpabSIEECzMIcQlzOPAMKsxMs7uhsnxPLuofPDTc1hkuq6MX9j16YU7CqegcYHbmWYuvAP6tCS97tgWf7dlQvnl25YPavXLVZvrzQPeHCpZmzzEUVq/xzu5sChnSTPTW7oOYmh69z4zL/gk3b+O6hoa733uviP82vnFcbqWlc9tDmZa23LVzaV1yXURi+JX+28NeBuj3+O8IrQ080Vm1eWB4OKjPmrJu7c1udWynvKF6/vs479lSW9+5gZkn+dKfellNGDPllzeULustz+A0bPvhgw7lkvEUwn/N4Ty7U7nhGsEpFkOfy+kutbOh1JQxhVDJumoW11hnkPThznh6FFlhfT+ra1x9sF56kx5YuDzVY9PQYAYA7iblw4frQ4TPCk2MK/xGU3rlmze62trHz6lsko+v+So/do74PT8KVkpJfOErKcv8znrMGsHTNxoEkWy1mYgDB6XBbPaWsuiS6CryGaL6zCjaXBgvtkuyXBua1wOKnh+k7L9AvPnYWffxK18FcJbuosGf3/Jo7amY+CE1vppzY+UTrva0FXc1i55pKQ/YjVL187N5fCn1kW5uot/1hi+DiZ+5atnJR9E+prvydJ9ZZ5mwOpU5gM4KYysMBQ71UzPuMTl9QQOyUo5nwioeYCPjFklrbK6s6X+ypUZ6rum9+CZYzWRiBJfSP0xzzSmrg7f86g0DKVj/wwFzieD9rRfPGFbeKMl05pn5j9/rsQJJ2iEgRrpohlyBo3f4QK7Kl+EcAYZgAoNVmZWXK704YAa3FwBxgSGUOs5htvGRz4Sgj3yFkSJFBuv/sxu5yk998T8WDJzvv/2RX19HtTUW1S+wpKRKRjJ6zzz/1/OPdFdWGlAKbvzS4PHOtURikg9AGz0LbIB85S/cPOpoXvuue8/iV2H1vPTy3ddvOeZ37HGmO3OmSzVzR+NS53+84dHlFhXPLqtzSO+5ruHM2vXtBdxP87LOzKAD359j/INYIbyPabIi3Cq6Wa+SaGe78diIzu7qcblcAa6/fJRvNopXFJnO+U9KKM5bqH5LM0iQSVmpPCPDu7ZT4Aoubz3709EBTyrTDjyx8MQXgUH1nqm7TWng4TzE4i4AsKskBITXfSyC4Fkl5MxnJDiKSIDSJAsGvd1y+/eNDp2e+A+5d8HeiiunrTkT6TqWLIs+/QRoWr98s0qj8uuzLuS22Ytufg3rdTaHn1m46sfgGKHXt0MGnLaRHdnwN37tvHcWKo2V6lnPxL4UvUQcRdOzmZSQs8X5CH5OxXMXpkATuDz8Et0SH4uyCRR+TjmBDP1GvsVrWEGVzEj33YVQ9jAtIKpqsl/s/0xrocwAAeJxjYGRgYADig3cEzsTz23xl4GZnAIHLRucNkWl2BrA4BwMTiAIAF4IITwB4nGNgZGBgZwCChWASxGZkQAXyABOUANh4nGNnYGBgHyAMADa8ANoAAAAAAAAOAFAAZgCyAMYA5gEeAUgBdAGcAfICLgKOAroDCgOOA7AD6gQ4BHwEuAToBQwFogXoBjYGbAbaB3IHqHicY2BkYGCQZ8hlYGcAASYg5gJCBob/YD4DABbVAaoAeJxdkE1qg0AYhl8Tk9AIoVDaVSmzahcF87PMARLIMoFAl0ZHY1BHdBJIT9AT9AQ9RQ9Qeqy+yteNMzDzfM+88w0K4BY/cNAMB6N2bUaPPBLukybCLvleeAAPj8JD+hfhMV7hC3u4wxs7OO4NzQSZcI/8Ltwnfwi75E/hAR7wJTyk/xYeY49fYQ/PztM+jbTZ7LY6OWdBJdX/pqs6NYWa+zMxa13oKrA6Uoerqi/JwtpYxZXJ1coUVmeZUWVlTjq0/tHacjmdxuL90OR8O0UEDYMNdtiSEpz5XQGqzlm30kzUdAYFFOb8R7NOZk0q2lwAyz1i7oAr1xoXvrOgtYhZx8wY5KRV269JZ5yGpmzPTjQhvY9je6vEElPOuJP3mWKnP5M3V+YAAAB4nG2P2XLCMAxFfYFspGUp3Te+IB9lHJF4cOzUS2n/voaEGR6qB+lKo+WITdhga/a/bRnDBFPMkCBFhhwF5ihxg1sssMQKa9xhg3s84BFPeMYLXvGGd3zgE9tZr/hveXKVkFYoSnoeHJXfRoWOqi54mo9ameNFdrK+dLSyaVf7oJQTlkhXpD3Z5XXhR/rUfQVuKXO91Jps4cLOS6/I5YL3XhodRRsVWZe4NnZOhWnSAWgxhMoEr6SmzZieF43Mk7ZOBdeCVGrp9Eu+54J2xhySplfB5XHwQLXUmT9KH6+kPnQ7ZYuIEzNyfs1DLU1VU4SWZ6LkXGHsD1ZKbMw=) format("woff"),url(data:application/x-font-ttf;charset=utf-8;base64,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FjY3PgE3LgEnIwYfAR4BFw4BBTM/ARUzESMGAyUVJwMchM2UWwgNq4JHrQgBAapUaAoJcWMBfiIhDiMrAQJLMB0BBAokNAIBPmMiIQ4iLAECSzAeAQUKJDQBP2MiIQ4iLAECSzAeAQUKJDQBAT75g+5B4arNLNIBJ44ByQL9BQ9mvYCKwA8FBQMDwwJVTGdzBf6VB8IHNR08lld9uT4LCRA/qGNxvUwHNR08lld9uT4LCRA/qGNxvUwHNR08lld9uT4LCRA/qGNxvVJkAWUDDEf+tYP5AQAAAAEAAAAABiAGtgAbAAABBAADER4BFzMRITU2ADcWABcVIREzPgE3EQIAA4D+4v6FBwJ/X+D+1QYBJ97eAScG/tXgX38CB/6FBrUH/oX+4v32X38CAlWV3gEnBgb+2d6V/asCf18CCgEeAXsAAAAAEADGAAEAAAAAAAEABwAAAAEAAAAAAAIABwAHAAEAAAAAAAMABwAOAAEAAAAAAAQABwAVAAEAAAAAAAUACwAcAAEAAAAAAAYABwAnAAEAAAAAAAoAKwAuAAEAAAAAAAsAEwBZAAMAAQQJAAEADgBsAAMAAQQJAAIADgB6AAMAAQQJAAMADgCIAAMAAQQJAAQADgCWAAMAAQQJAAUAFgCkAAMAAQQJAAYADgC6AAMAAQQJAAoAVgDIAAMAAQQJAAsAJgEeVmlkZW9KU1JlZ3VsYXJWaWRlb0pTVmlkZW9KU1ZlcnNpb24gMS4wVmlkZW9KU0dlbmVyYXRlZCBieSBzdmcydHRmIGZyb20gRm9udGVsbG8gcHJvamVjdC5odHRwOi8vZm9udGVsbG8uY29tAFYAaQBkAGUAbwBKAFMAUgBlAGcAdQBsAGEAcgBWAGkAZABlAG8ASgBTAFYAaQBkAGUAbwBKAFMAVgBlAHIAcwBpAG8AbgAgADEALgAwAFYAaQBkAGUAbwBKAFMARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABzAHYAZwAyAHQAdABmACAAZgByAG8AbQAgAEYAbwBuAHQAZQBsAGwAbwAgAHAAcgBvAGoAZQBjAHQALgBoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAAAAAgAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAABAgEDAQQBBQEGAQcBCAEJAQoBCwEMAQ0BDgEPARABEQESARMBFAEVARYBFwEYARkBGgEbARwBHQEeAR8EcGxheQtwbGF5LWNpcmNsZQVwYXVzZQt2b2x1bWUtbXV0ZQp2b2x1bWUtbG93CnZvbHVtZS1taWQLdm9sdW1lLWhpZ2gQZnVsbHNjcmVlbi1lbnRlcg9mdWxsc2NyZWVuLWV4aXQGc3F1YXJlB3NwaW5uZXIJc3VidGl0bGVzCGNhcHRpb25zCGNoYXB0ZXJzBXNoYXJlA2NvZwZjaXJjbGUOY2lyY2xlLW91dGxpbmUTY2lyY2xlLWlubmVyLWNpcmNsZQJoZAZjYW5jZWwGcmVwbGF5CGZhY2Vib29rBWdwbHVzCGxpbmtlZGluB3R3aXR0ZXIGdHVtYmxyCXBpbnRlcmVzdBFhdWRpby1kZXNjcmlwdGlvbgVhdWRpbwAAAAAA) format("truetype");font-weight:400;font-style:normal}.video-js .vjs-big-play-button:before,.video-js .vjs-play-control:before,.vjs-icon-play:before{content:"\f101"}.vjs-icon-play-circle:before{content:"\f102"}.video-js .vjs-play-control.vjs-playing:before,.vjs-icon-pause:before{content:"\f103"}.video-js .vjs-mute-control.vjs-vol-0:before,.video-js .vjs-volume-menu-button.vjs-vol-0:before,.vjs-icon-volume-mute:before{content:"\f104"}.video-js .vjs-mute-control.vjs-vol-1:before,.video-js .vjs-volume-menu-button.vjs-vol-1:before,.vjs-icon-volume-low:before{content:"\f105"}.video-js .vjs-mute-control.vjs-vol-2:before,.video-js .vjs-volume-menu-button.vjs-vol-2:before,.vjs-icon-volume-mid:before{content:"\f106"}.video-js .vjs-mute-control:before,.video-js .vjs-volume-menu-button:before,.vjs-icon-volume-high:before{content:"\f107"}.video-js .vjs-fullscreen-control:before,.vjs-icon-fullscreen-enter:before{content:"\f108"}.video-js.vjs-fullscreen .vjs-fullscreen-control:before,.vjs-icon-fullscreen-exit:before{content:"\f109"}.vjs-icon-square:before{content:"\f10a"}.vjs-icon-spinner:before{content:"\f10b"}.video-js .vjs-subtitles-button:before,.vjs-icon-subtitles:before{content:"\f10c"}.video-js .vjs-captions-button:before,.vjs-icon-captions:before{content:"\f10d"}.video-js .vjs-chapters-button:before,.vjs-icon-chapters:before{content:"\f10e"}.vjs-icon-share{font-family:VideoJS;font-weight:400;font-style:normal}.vjs-icon-share:before{content:"\f10f"}.vjs-icon-cog:before{content:"\f110"}.video-js .vjs-mouse-display:before,.video-js .vjs-play-progress:before,.video-js .vjs-volume-level:before,.vjs-icon-circle:before{content:"\f111"}.vjs-icon-circle-outline:before{content:"\f112"}.vjs-icon-circle-inner-circle:before{content:"\f113"}.vjs-icon-hd:before{content:"\f114"}.video-js .vjs-control.vjs-close-button:before,.vjs-icon-cancel:before{content:"\f115"}.vjs-icon-replay:before{content:"\f116"}.vjs-icon-facebook:before{content:"\f117"}.vjs-icon-gplus:before{content:"\f118"}.vjs-icon-linkedin:before{content:"\f119"}.vjs-icon-twitter:before{content:"\f11a"}.vjs-icon-tumblr:before{content:"\f11b"}.vjs-icon-pinterest:before{content:"\f11c"}.video-js .vjs-descriptions-button:before,.vjs-icon-audio-description:before{content:"\f11d"}.video-js .vjs-audio-button:before,.vjs-icon-audio:before{content:"\f11e"}.video-js{display:block;vertical-align:top;box-sizing:border-box;position:relative;padding:0;font-size:10px;line-height:1;font-weight:400;font-style:normal;font-family:Arial,Helvetica,sans-serif;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.video-js:-moz-full-screen{position:absolute}.video-js:-webkit-full-screen{width:100%!important;height:100%!important}.video-js .vjs-tech,.video-js.vjs-fill{width:100%;height:100%}.video-js *,.video-js :after,.video-js :before{box-sizing:inherit}.video-js ul{font-family:inherit;font-size:inherit;line-height:inherit;list-style-position:outside;margin:0}.vjs-error .vjs-error-display:before,.vjs-menu .vjs-menu-content,.vjs-no-js{font-family:Arial,Helvetica,sans-serif}.video-js.vjs-16-9,.video-js.vjs-4-3,.video-js.vjs-fluid{width:100%;max-width:100%;height:0}.video-js.vjs-16-9{padding-top:56.25%}.video-js.vjs-4-3{padding-top:75%}.video-js .vjs-tech{position:absolute;top:0;left:0}body.vjs-full-window{padding:0;margin:0;height:100%;overflow-y:auto}.vjs-full-window .video-js.vjs-fullscreen{position:fixed;overflow:hidden;z-index:1000;left:0;top:0;bottom:0;right:0}.video-js.vjs-fullscreen{width:100%!important;height:100%!important;padding-top:0!important}.video-js.vjs-fullscreen.vjs-user-inactive{cursor:none}.vjs-hidden{display:none!important}.vjs-disabled{opacity:.5;cursor:default}.video-js .vjs-offscreen{height:1px;left:-9999px;position:absolute;top:0;width:1px}.vjs-lock-showing{display:block!important;opacity:1;visibility:visible}.vjs-no-js{padding:20px;font-size:18px;width:300px;height:150px;margin:0 auto}.vjs-no-js a,.vjs-no-js a:visited{color:#66A8CC}.video-js .vjs-big-play-button{font-size:3em;line-height:1.5em;height:1.5em;width:3em;display:block;position:absolute;top:10px;left:10px;padding:0;cursor:pointer;opacity:1;border:.06666em solid #fff;background-color:#2B333F;background-color:rgba(43,51,63,.7);-webkit-border-radius:.3em;-moz-border-radius:.3em;border-radius:.3em;-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;transition:all .4s}.vjs-big-play-centered .vjs-big-play-button{top:50%;left:50%;margin-top:-.75em;margin-left:-1.5em}.video-js .vjs-big-play-button:focus,.video-js:hover .vjs-big-play-button{outline:0;border-color:#fff;background-color:#73859f;background-color:rgba(115,133,159,.5);-webkit-transition:all 0s;-moz-transition:all 0s;-o-transition:all 0s;transition:all 0s}.vjs-controls-disabled .vjs-big-play-button,.vjs-error .vjs-big-play-button,.vjs-has-started .vjs-big-play-button,.vjs-using-native-controls .vjs-big-play-button{display:none}.video-js button{background:0 0;border:none;color:inherit;display:inline-block;overflow:visible;font-size:inherit;line-height:inherit;text-transform:none;text-decoration:none;transition:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.video-js .vjs-control.vjs-close-button{cursor:pointer;height:3em;position:absolute;right:0;top:.5em;z-index:2}.vjs-menu-button{cursor:pointer}.vjs-menu-button.vjs-disabled{cursor:default}.vjs-workinghover .vjs-menu-button.vjs-disabled:hover .vjs-menu{display:none}.vjs-menu .vjs-menu-content{display:block;padding:0;margin:0;overflow:auto}.vjs-scrubbing .vjs-menu-button:hover .vjs-menu{display:none}.vjs-menu li{list-style:none;margin:0;padding:.2em 0;line-height:1.4em;font-size:1.2em;text-transform:lowercase}.vjs-menu li:focus,.vjs-menu li:hover{outline:0;background-color:#73859f;background-color:rgba(115,133,159,.5)}.vjs-menu li.vjs-selected,.vjs-menu li.vjs-selected:focus,.vjs-menu li.vjs-selected:hover{background-color:#fff;color:#2B333F}.vjs-menu li.vjs-menu-title{text-align:center;text-transform:uppercase;font-size:1em;line-height:2em;padding:0;margin:0 0 .3em;font-weight:700;cursor:default}.vjs-menu-button-popup .vjs-menu{display:none;position:absolute;bottom:0;width:10em;left:-3em;height:0;margin-bottom:1.5em;border-top-color:rgba(43,51,63,.7)}.vjs-menu-button-popup .vjs-menu .vjs-menu-content{background-color:#2B333F;background-color:rgba(43,51,63,.7);position:absolute;width:100%;bottom:1.5em;max-height:15em}.vjs-menu-button-popup .vjs-menu.vjs-lock-showing,.vjs-workinghover .vjs-menu-button-popup:hover .vjs-menu{display:block}.video-js .vjs-menu-button-inline{-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;transition:all .4s;overflow:hidden}.video-js .vjs-menu-button-inline:before{width:2.222222222em}.video-js .vjs-menu-button-inline.vjs-slider-active,.video-js .vjs-menu-button-inline:focus,.video-js .vjs-menu-button-inline:hover,.video-js.vjs-no-flex .vjs-menu-button-inline{width:12em}.video-js .vjs-menu-button-inline.vjs-slider-active{-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.vjs-menu-button-inline .vjs-menu{opacity:0;height:100%;width:auto;position:absolute;left:4em;top:0;padding:0;margin:0;-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;transition:all .4s}.vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-menu-button-inline:focus .vjs-menu,.vjs-menu-button-inline:hover .vjs-menu{display:block;opacity:1}.vjs-no-flex .vjs-menu-button-inline .vjs-menu{display:block;opacity:1;position:relative;width:auto}.vjs-no-flex .vjs-menu-button-inline.vjs-slider-active .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:focus .vjs-menu,.vjs-no-flex .vjs-menu-button-inline:hover .vjs-menu{width:auto}.vjs-menu-button-inline .vjs-menu-content{width:auto;height:100%;margin:0;overflow:hidden}.video-js .vjs-control-bar{display:none;width:100%;position:absolute;bottom:0;left:0;right:0;height:3em;background-color:#2B333F;background-color:rgba(43,51,63,.7)}.vjs-has-started .vjs-control-bar{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;visibility:visible;opacity:1;-webkit-transition:visibility .1s,opacity .1s;-moz-transition:visibility .1s,opacity .1s;-o-transition:visibility .1s,opacity .1s;transition:visibility .1s,opacity .1s}.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{visibility:visible;opacity:0;-webkit-transition:visibility 1s,opacity 1s;-moz-transition:visibility 1s,opacity 1s;-o-transition:visibility 1s,opacity 1s;transition:visibility 1s,opacity 1s}@media \0screen{.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{visibility:hidden}.vjs-user-inactive.vjs-playing .vjs-control-bar :before{content:""}}.vjs-controls-disabled .vjs-control-bar,.vjs-error .vjs-control-bar,.vjs-using-native-controls .vjs-control-bar{display:none!important}.vjs-audio.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{opacity:1;visibility:visible}.vjs-has-started.vjs-no-flex .vjs-control-bar{display:table}.video-js .vjs-control{outline:0;position:relative;margin:0;padding:0;height:100%;width:4em;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none}.video-js .vjs-control:before{font-size:1.8em;line-height:1.67}.video-js .vjs-control:focus,.video-js .vjs-control:focus:before,.video-js .vjs-control:hover:before{text-shadow:0 0 1em #fff}.video-js .vjs-control-text{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.vjs-no-flex .vjs-control{display:table-cell;vertical-align:middle}.video-js .vjs-custom-control-spacer{display:none}.video-js .vjs-progress-control{-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;min-width:4em}.vjs-live .vjs-progress-control{display:none}.video-js .vjs-progress-holder{-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto;-webkit-transition:all .2s;-moz-transition:all .2s;-o-transition:all .2s;transition:all .2s;height:.3em}.video-js .vjs-progress-control:hover .vjs-progress-holder{font-size:1.666666666666666666em}.video-js .vjs-progress-control:hover .vjs-mouse-display:after,.video-js .vjs-progress-control:hover .vjs-play-progress:after,.video-js .vjs-progress-control:hover .vjs-time-tooltip{font-family:Arial,Helvetica,sans-serif;visibility:visible;font-size:.6em}.video-js .vjs-progress-holder .vjs-load-progress,.video-js .vjs-progress-holder .vjs-load-progress div,.video-js .vjs-progress-holder .vjs-play-progress,.video-js .vjs-progress-holder .vjs-tooltip-progress-bar{position:absolute;display:block;height:.3em;margin:0;padding:0;width:0;left:0;top:0}.video-js .vjs-mouse-display:before,.video-js .vjs-progress-control .vjs-keep-tooltips-inside:after{display:none}.video-js .vjs-play-progress{background-color:#fff}.video-js .vjs-play-progress:before{position:absolute;top:-.333333333333333em;right:-.5em;font-size:.9em}.video-js .vjs-mouse-display:after,.video-js .vjs-play-progress:after,.video-js .vjs-time-tooltip{visibility:hidden;pointer-events:none;position:absolute;top:-3.4em;right:-1.9em;font-size:.9em;color:#000;content:attr(data-current-time);padding:6px 8px 8px;background-color:#fff;background-color:rgba(255,255,255,.8);-webkit-border-radius:.3em;-moz-border-radius:.3em;border-radius:.3em}.video-js .vjs-play-progress:after,.video-js .vjs-play-progress:before,.video-js .vjs-time-tooltip{z-index:1}.video-js .vjs-load-progress{background:#bfc7d3;background:rgba(115,133,159,.5)}.video-js .vjs-load-progress div{background:#fff;background:rgba(115,133,159,.75)}.video-js.vjs-no-flex .vjs-progress-control{width:auto}.video-js .vjs-time-tooltip{display:inline-block;height:2.4em;position:relative;float:right;right:-1.9em}.vjs-tooltip-progress-bar{visibility:hidden}.video-js .vjs-progress-control .vjs-mouse-display{display:none;position:absolute;width:1px;height:100%;background-color:#000;z-index:1}.vjs-no-flex .vjs-progress-control .vjs-mouse-display{z-index:0}.video-js .vjs-progress-control:hover .vjs-mouse-display{display:block}.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display,.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display:after{visibility:hidden;opacity:0;-webkit-transition:visibility 1s,opacity 1s;-moz-transition:visibility 1s,opacity 1s;-o-transition:visibility 1s,opacity 1s;transition:visibility 1s,opacity 1s}.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display,.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display:after{display:none}.video-js .vjs-progress-control .vjs-mouse-display:after,.vjs-mouse-display .vjs-time-tooltip{color:#fff;background-color:#000;background-color:rgba(0,0,0,.8)}.video-js .vjs-slider{outline:0;position:relative;cursor:pointer;padding:0;margin:0 .45em;background-color:#73859f;background-color:rgba(115,133,159,.5)}.video-js .vjs-slider:focus{text-shadow:0 0 1em #fff;-webkit-box-shadow:0 0 1em #fff;-moz-box-shadow:0 0 1em #fff;box-shadow:0 0 1em #fff}.video-js .vjs-mute-control,.video-js .vjs-volume-menu-button{cursor:pointer;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none}.video-js .vjs-volume-control{width:5em;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.video-js .vjs-volume-bar{margin:1.35em .45em}.vjs-volume-bar.vjs-slider-horizontal{width:5em;height:.3em}.vjs-volume-bar.vjs-slider-vertical{width:.3em;height:5em;margin:1.35em auto}.video-js .vjs-volume-level{position:absolute;bottom:0;left:0;background-color:#fff}.video-js .vjs-volume-level:before{position:absolute;font-size:.9em}.vjs-slider-vertical .vjs-volume-level{width:.3em}.vjs-slider-vertical .vjs-volume-level:before{top:-.5em;left:-.3em}.vjs-slider-horizontal .vjs-volume-level{height:.3em}.vjs-slider-horizontal .vjs-volume-level:before{top:-.3em;right:-.5em}.vjs-volume-bar.vjs-slider-vertical .vjs-volume-level{height:100%}.vjs-volume-bar.vjs-slider-horizontal .vjs-volume-level{width:100%}.vjs-menu-button-popup.vjs-volume-menu-button .vjs-menu{display:block;width:0;height:0;border-top-color:transparent}.vjs-menu-button-popup.vjs-volume-menu-button-vertical .vjs-menu{left:.5em;height:8em}.vjs-menu-button-popup.vjs-volume-menu-button-horizontal .vjs-menu{left:-2em}.vjs-menu-button-popup.vjs-volume-menu-button .vjs-menu-content{height:0;width:0;overflow-x:hidden;overflow-y:hidden}.vjs-volume-menu-button-vertical .vjs-lock-showing .vjs-menu-content,.vjs-volume-menu-button-vertical.vjs-slider-active .vjs-menu-content,.vjs-volume-menu-button-vertical:focus .vjs-menu-content,.vjs-volume-menu-button-vertical:hover .vjs-menu-content{height:8em;width:2.9em}.vjs-volume-menu-button-horizontal .vjs-lock-showing .vjs-menu-content,.vjs-volume-menu-button-horizontal .vjs-slider-active .vjs-menu-content,.vjs-volume-menu-button-horizontal:focus .vjs-menu-content,.vjs-volume-menu-button-horizontal:hover .vjs-menu-content{height:2.9em;width:8em}.vjs-volume-menu-button.vjs-menu-button-inline .vjs-menu-content{background-color:transparent!important}.vjs-poster{display:inline-block;vertical-align:middle;background-repeat:no-repeat;background-position:50% 50%;background-size:contain;background-color:#000;cursor:pointer;margin:0;padding:0;position:absolute;top:0;right:0;bottom:0;left:0;height:100%}.vjs-poster img{display:block;vertical-align:middle;margin:0 auto;max-height:100%;padding:0;width:100%}.vjs-hide-poster .vjs-poster,.vjs-playing .vjs-poster{display:none}.vjs-audio.vjs-has-started .vjs-poster{display:block}.vjs-using-native-controls .vjs-poster{display:none}.video-js .vjs-live-control{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:flex-start;-webkit-align-items:flex-start;-ms-flex-align:flex-start;align-items:flex-start;-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto;font-size:1em;line-height:3em}.vjs-no-flex .vjs-live-control{display:table-cell;width:auto;text-align:left}.vjs-live .vjs-time-control,.vjs-live .vjs-time-divider{display:none}.video-js .vjs-time-control{-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none;font-size:1em;line-height:3em;min-width:2em;width:auto;padding-left:1em;padding-right:1em}.video-js .vjs-current-time,.vjs-no-flex .vjs-current-time{padding-right:0}.video-js .vjs-duration,.vjs-no-flex .vjs-duration{padding-left:0}.video-js .vjs-time-divider{line-height:3em;padding-left:.5em;padding-right:.5em;min-width:1em}.video-js .vjs-play-control{cursor:pointer;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none}.vjs-text-track-display{position:absolute;bottom:3em;left:0;right:0;top:0;pointer-events:none}.video-js.vjs-user-inactive.vjs-playing .vjs-text-track-display{bottom:1em}.video-js .vjs-text-track{font-size:1.4em;text-align:center;margin-bottom:.1em;background-color:#000;background-color:rgba(0,0,0,.5)}.vjs-subtitles{color:#fff}.vjs-captions{color:#fc6}.vjs-tt-cue{display:block}video::-webkit-media-text-track-display{-moz-transform:translateY(-3em);-ms-transform:translateY(-3em);-o-transform:translateY(-3em);-webkit-transform:translateY(-3em);transform:translateY(-3em)}.video-js.vjs-user-inactive.vjs-playing video::-webkit-media-text-track-display{-moz-transform:translateY(-1.5em);-ms-transform:translateY(-1.5em);-o-transform:translateY(-1.5em);-webkit-transform:translateY(-1.5em);transform:translateY(-1.5em)}.video-js .vjs-fullscreen-control{cursor:pointer;-webkit-box-flex:none;-moz-box-flex:none;-webkit-flex:none;-ms-flex:none;flex:none}.vjs-playback-rate .vjs-playback-rate-value{font-size:1.5em;line-height:2;position:absolute;top:0;left:0;width:100%;height:100%;text-align:center}.vjs-playback-rate .vjs-menu{width:4em;left:0}.vjs-error .vjs-error-display .vjs-modal-dialog-content{font-size:1.4em;text-align:center}.vjs-error .vjs-error-display:before{color:#fff;content:'X';font-size:4em;left:0;line-height:1;margin-top:-.5em;position:absolute;text-shadow:.05em .05em .1em #000;text-align:center;top:50%;vertical-align:middle;width:100%}.vjs-loading-spinner{display:none;position:absolute;top:50%;left:50%;margin:-25px 0 0 -25px;opacity:.85;text-align:left;border:6px solid rgba(43,51,63,.7);box-sizing:border-box;background-clip:padding-box;width:50px;height:50px;border-radius:25px}.vjs-seeking .vjs-loading-spinner,.vjs-waiting .vjs-loading-spinner{display:block}.vjs-loading-spinner:after,.vjs-loading-spinner:before{content:"";position:absolute;margin:-6px;box-sizing:inherit;width:inherit;height:inherit;border-radius:inherit;opacity:1;border:inherit;border-color:#fff transparent transparent}.vjs-seeking .vjs-loading-spinner:after,.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:before{-webkit-animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite;animation:vjs-spinner-spin 1.1s cubic-bezier(.6,.2,0,.8) infinite,vjs-spinner-fade 1.1s linear infinite}.vjs-seeking .vjs-loading-spinner:before,.vjs-waiting .vjs-loading-spinner:before{border-top-color:#fff}.vjs-seeking .vjs-loading-spinner:after,.vjs-waiting .vjs-loading-spinner:after{border-top-color:#fff;-webkit-animation-delay:.44s;animation-delay:.44s}@keyframes vjs-spinner-spin{100%{transform:rotate(360deg)}}@-webkit-keyframes vjs-spinner-spin{100%{-webkit-transform:rotate(360deg)}}@keyframes vjs-spinner-fade{0%,100%,20%,60%{border-top-color:#73859f}35%{border-top-color:#fff}}@-webkit-keyframes vjs-spinner-fade{0%,100%,20%,60%{border-top-color:#73859f}35%{border-top-color:#fff}}.vjs-chapters-button .vjs-menu ul{width:24em}.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-custom-control-spacer{-webkit-box-flex:auto;-moz-box-flex:auto;-webkit-flex:auto;-ms-flex:auto;flex:auto}.video-js.vjs-layout-tiny:not(.vjs-fullscreen).vjs-no-flex .vjs-custom-control-spacer{width:auto}.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-subtitles-button,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-small:not(.vjs-fullscreen) .vjs-volume-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-progress-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-subtitles-button,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-volume-control,.video-js.vjs-layout-tiny:not(.vjs-fullscreen) .vjs-volume-menu-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-captions-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-chapters-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-current-time,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-duration,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-mute-control,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-playback-rate,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-remaining-time,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-subtitles-button,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-time-divider,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-volume-control,.video-js.vjs-layout-x-small:not(.vjs-fullscreen) .vjs-volume-menu-button{display:none}.vjs-caption-settings{position:relative;top:1em;background-color:#2B333F;background-color:rgba(43,51,63,.75);color:#fff;margin:0 auto;padding:.5em;height:15em;font-size:12px;width:40em}.vjs-caption-settings .vjs-tracksettings{top:0;bottom:2em;left:0;right:0;position:absolute;overflow:auto}.vjs-caption-settings .vjs-tracksettings-colors,.vjs-caption-settings .vjs-tracksettings-font{float:left}.vjs-caption-settings .vjs-tracksettings-colors:after,.vjs-caption-settings .vjs-tracksettings-controls:after,.vjs-caption-settings .vjs-tracksettings-font:after{clear:both}.vjs-caption-settings .vjs-tracksettings-controls{position:absolute;bottom:1em;right:1em}.vjs-caption-settings .vjs-tracksetting{margin:5px;padding:3px;min-height:40px}.vjs-caption-settings .vjs-tracksetting label{display:block;width:100px;margin-bottom:5px}.vjs-caption-settings .vjs-tracksetting span{display:inline;margin-left:5px}.vjs-caption-settings .vjs-tracksetting>div{margin-bottom:5px;min-height:20px}.vjs-caption-settings .vjs-tracksetting>div:last-child{margin-bottom:0;padding-bottom:0;min-height:0}.vjs-caption-settings label>input{margin-right:10px}.vjs-caption-settings input[type=button]{width:40px;height:40px}.video-js .vjs-modal-dialog{background:rgba(0,0,0,.8);background:-webkit-linear-gradient(-90deg,rgba(0,0,0,.8),rgba(255,255,255,0));background:linear-gradient(180deg,rgba(0,0,0,.8),rgba(255,255,255,0))}.vjs-modal-dialog .vjs-modal-dialog-content{font-size:1.2em;line-height:1.5;padding:20px 24px;z-index:1}
--------------------------------------------------------------------------------