├── 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 | 29 | {items.map((item, index) => { 30 | return ( 31 | 32 | 33 | 34 | 35 | {item.text} 36 | 37 | 38 | 39 | ) 40 | })} 41 | 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 | {imgName} 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 |
47 |
48 |
49 | 50 | 51 | 52 | 53 | {getFieldDecorator('userName', { 54 | rules: [{ required: true, message: 'Please input your username!' }], 55 | })( 56 | } placeholder="Username" /> 57 | )} 58 | 59 | 60 | {getFieldDecorator('password', { 61 | rules: [{ required: true, message: 'Please input your Password!' }], 62 | })( 63 | } type="password" placeholder="Password" /> 64 | )} 65 | 66 | 67 | 70 | 71 |
72 |
73 |
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 | 12 | {nodeItems.map((nodeItem, i) => ( 13 | {nodeItem.display} 14 | ))} 15 | 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 |
      167 | 173 |
    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 |
    227 | 228 | 229 | 231 | {this.courseCase.fileName} 232 |
    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 |
    216 | 218 | {this.courseCase.fileName} 219 |
    220 |
    221 | 222 | {this.LivePlayerRender()} 223 | 224 |
    225 | 226 |
    227 | 229 | {this.learningCase.fileName} 230 |
    231 |
    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} --------------------------------------------------------------------------------