From 598a7eb516d12671385c9a3eb9d4dce590bb5cf8 Mon Sep 17 00:00:00 2001 From: Brain Bag Date: Wed, 10 Apr 2019 09:35:44 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=AD=94=E9=A2=98=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=8C=E8=83=BD=E5=A4=9F=E8=BF=9B=E8=A1=8C=E7=AD=94?= =?UTF-8?q?=E9=A2=98=E5=90=8E=E7=9A=84=E5=AD=A6=E4=B9=A0=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E6=8E=A8=E8=8D=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + public/index.html | 2 + server/database/models/mOnlineEditor.js | 9 +- server/public/resource/default-thumbnail.png | Bin 0 -> 9387 bytes server/routes/api/index.js | 7 + server/routes/api/knowledgePoints/index.js | 104 + server/routes/api/service/index.js | 62 + server/routes/index.js | 13 +- server/routes/material/uploadMaterial.js | 48 +- server/routes/utils/pyRequest.js | 55 + src/course-manage/CourseContent.js | 157 +- src/course-manage/CourseItem.js | 4 +- src/course-manage/CourseList.js | 9 +- src/course-manage/LearnerCourseRoute.js | 26 +- src/course-manage/index.css | 31 +- src/course-manage/index.js | 6 +- src/course-manage/knowledgePreview.js | 391 - src/course-manage/knowledgePreviewPage.js | 375 + .../materialDisplayComponents.js | 296 + src/editor/componentConstructor.js | 2 +- src/editor/editor.css | 2 + src/editor/teachunit-editor.js | 350 +- src/learningPage/index.js | 16 +- src/material/MaterialList.js | 2 +- src/material/QuizFormItems.js | 428 + src/publicComponents/DecimalStep.js | 34 + src/publicComponents/RichTextEditor.js | 66 + src/searchManager/SearchList.js | 5 +- src/searchManager/searchList_wbw.js | 354 - src/upload/FileUploaderModal.js | 4 +- src/upload/filesupload.js | 333 +- src/utils/Vizuly/index.js | 503 - src/utils/Vizuly/lib/cssmenu.js | 115 - src/utils/Vizuly/lib/jquery-2.1.1.min.js | 4 - src/utils/Vizuly/lib/materialize/LICENSE | 21 - src/utils/Vizuly/lib/materialize/README.md | 60 - .../lib/materialize/css/materialize.css | 8281 ----------------- .../lib/materialize/css/materialize.min.css | 16 - .../font/material-design-icons/LICENSE.txt | 428 - .../Material-Design-Icons.eot | Bin 102112 -> 0 bytes .../Material-Design-Icons.svg | 769 -- .../Material-Design-Icons.ttf | Bin 101892 -> 0 bytes .../Material-Design-Icons.woff | Bin 101968 -> 0 bytes .../Material-Design-Icons.woff2 | Bin 33220 -> 0 bytes .../materialize/font/roboto/Roboto-Bold.ttf | Bin 127744 -> 0 bytes .../materialize/font/roboto/Roboto-Bold.woff | Bin 62876 -> 0 bytes .../materialize/font/roboto/Roboto-Bold.woff2 | Bin 49976 -> 0 bytes .../materialize/font/roboto/Roboto-Light.ttf | Bin 126792 -> 0 bytes .../materialize/font/roboto/Roboto-Light.woff | Bin 62316 -> 0 bytes .../font/roboto/Roboto-Light.woff2 | Bin 49380 -> 0 bytes .../materialize/font/roboto/Roboto-Medium.ttf | Bin 127488 -> 0 bytes .../font/roboto/Roboto-Medium.woff | Bin 62980 -> 0 bytes .../font/roboto/Roboto-Medium.woff2 | Bin 50224 -> 0 bytes .../font/roboto/Roboto-Regular.ttf | Bin 126072 -> 0 bytes .../font/roboto/Roboto-Regular.woff | Bin 61736 -> 0 bytes .../font/roboto/Roboto-Regular.woff2 | Bin 49236 -> 0 bytes .../materialize/font/roboto/Roboto-Thin.ttf | Bin 127584 -> 0 bytes .../materialize/font/roboto/Roboto-Thin.woff | Bin 61628 -> 0 bytes .../materialize/font/roboto/Roboto-Thin.woff2 | Bin 48524 -> 0 bytes .../Vizuly/lib/materialize/js/materialize.js | 6478 ------------- .../lib/materialize/js/materialize.min.js | 10 - src/utils/Vizuly/lib/styles/cssmenu.css | 382 - .../Vizuly/lib/styles/img/logo_16x16.png | Bin 909 -> 0 bytes .../Vizuly/lib/styles/img/logo_274x63_2.png | Bin 8511 -> 0 bytes .../lib/styles/img/logo_vertical_dark.png | Bin 11356 -> 0 bytes .../lib/styles/img/vz-skin-galaxy_2.jpg | Bin 415508 -> 0 bytes src/utils/Vizuly/lib/styles/vizuly.css | 145 - src/utils/Vizuly/lib/styles/vizuly_halo.css | 48 - .../lib/styles/vizuly_radial_progress.css | 90 - .../Vizuly/lib/styles/vizuly_scatter.css | 20 - .../Vizuly/lib/styles/vizuly_weightedtree.css | 56 - src/utils/Vizuly/lib/vizuly_core.min.js | 6 - .../Vizuly/weightedtree/WeightedtreeTest.html | 144 - .../data/weightedtree_federal_budget.csv | 1339 --- src/utils/Vizuly/weightedtree/lib/d3.min.js | 5 - .../Vizuly/weightedtree/lib/theme_showreel.js | 69 - .../weightedtree/lib/vizuly_core.min.js | 6 - .../lib/vizuly_weightedtree.min.js | 4 - .../weightedtree/src/core/_namespace.js | 58 - .../Vizuly/weightedtree/src/core/component.js | 193 - .../Vizuly/weightedtree/src/core/util.js | 280 - .../Vizuly/weightedtree/src/svg/filter.js | 62 - .../Vizuly/weightedtree/src/svg/gradient.js | 206 - src/utils/Vizuly/weightedtree/src/svg/text.js | 94 - .../weightedtree/src/theme/column_bar.js | 577 -- .../Vizuly/weightedtree/src/theme/corona.js | 426 - .../Vizuly/weightedtree/src/theme/halo.js | 466 - .../Vizuly/weightedtree/src/theme/linearea.js | 414 - .../weightedtree/src/theme/radialprogress.js | 367 - .../weightedtree/src/theme/rangeinput.js | 138 - .../Vizuly/weightedtree/src/theme/scatter.js | 334 - .../weightedtree/src/theme/weightedtree.js | 285 - .../Vizuly/weightedtree/src/ui/rangeinput.js | 154 - .../weightedtree/src/viz/weightedtree.js | 534 -- .../Vizuly/weightedtree/styles/examples.css | 220 - .../Vizuly/weightedtree/weightedtree_test.js | 260 - src/utils/index.js | 3 - src/utils/netService/index.js | 15 + src/utils/netService/request.js | 2 - yarn.lock | 1069 ++- 100 files changed, 2683 insertions(+), 25666 deletions(-) create mode 100644 server/public/resource/default-thumbnail.png create mode 100644 server/routes/api/index.js create mode 100644 server/routes/api/knowledgePoints/index.js create mode 100644 server/routes/api/service/index.js create mode 100644 server/routes/utils/pyRequest.js delete mode 100644 src/course-manage/knowledgePreview.js create mode 100644 src/course-manage/knowledgePreviewPage.js create mode 100644 src/course-manage/materialDisplayComponents.js create mode 100644 src/material/QuizFormItems.js create mode 100644 src/publicComponents/DecimalStep.js create mode 100644 src/publicComponents/RichTextEditor.js delete mode 100644 src/searchManager/searchList_wbw.js delete mode 100644 src/utils/Vizuly/index.js delete mode 100644 src/utils/Vizuly/lib/cssmenu.js delete mode 100644 src/utils/Vizuly/lib/jquery-2.1.1.min.js delete mode 100644 src/utils/Vizuly/lib/materialize/LICENSE delete mode 100644 src/utils/Vizuly/lib/materialize/README.md delete mode 100644 src/utils/Vizuly/lib/materialize/css/materialize.css delete mode 100644 src/utils/Vizuly/lib/materialize/css/materialize.min.css delete mode 100644 src/utils/Vizuly/lib/materialize/font/material-design-icons/LICENSE.txt delete mode 100644 src/utils/Vizuly/lib/materialize/font/material-design-icons/Material-Design-Icons.eot delete mode 100644 src/utils/Vizuly/lib/materialize/font/material-design-icons/Material-Design-Icons.svg delete mode 100644 src/utils/Vizuly/lib/materialize/font/material-design-icons/Material-Design-Icons.ttf delete mode 100644 src/utils/Vizuly/lib/materialize/font/material-design-icons/Material-Design-Icons.woff delete mode 100644 src/utils/Vizuly/lib/materialize/font/material-design-icons/Material-Design-Icons.woff2 delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Bold.ttf delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Bold.woff delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Bold.woff2 delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Light.ttf delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Light.woff delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Light.woff2 delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Medium.ttf delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Medium.woff delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Medium.woff2 delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Regular.ttf delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Regular.woff delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Regular.woff2 delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Thin.ttf delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Thin.woff delete mode 100644 src/utils/Vizuly/lib/materialize/font/roboto/Roboto-Thin.woff2 delete mode 100644 src/utils/Vizuly/lib/materialize/js/materialize.js delete mode 100644 src/utils/Vizuly/lib/materialize/js/materialize.min.js delete mode 100644 src/utils/Vizuly/lib/styles/cssmenu.css delete mode 100644 src/utils/Vizuly/lib/styles/img/logo_16x16.png delete mode 100644 src/utils/Vizuly/lib/styles/img/logo_274x63_2.png delete mode 100644 src/utils/Vizuly/lib/styles/img/logo_vertical_dark.png delete mode 100644 src/utils/Vizuly/lib/styles/img/vz-skin-galaxy_2.jpg delete mode 100644 src/utils/Vizuly/lib/styles/vizuly.css delete mode 100644 src/utils/Vizuly/lib/styles/vizuly_halo.css delete mode 100644 src/utils/Vizuly/lib/styles/vizuly_radial_progress.css delete mode 100644 src/utils/Vizuly/lib/styles/vizuly_scatter.css delete mode 100644 src/utils/Vizuly/lib/styles/vizuly_weightedtree.css delete mode 100644 src/utils/Vizuly/lib/vizuly_core.min.js delete mode 100644 src/utils/Vizuly/weightedtree/WeightedtreeTest.html delete mode 100644 src/utils/Vizuly/weightedtree/data/weightedtree_federal_budget.csv delete mode 100644 src/utils/Vizuly/weightedtree/lib/d3.min.js delete mode 100644 src/utils/Vizuly/weightedtree/lib/theme_showreel.js delete mode 100644 src/utils/Vizuly/weightedtree/lib/vizuly_core.min.js delete mode 100644 src/utils/Vizuly/weightedtree/lib/vizuly_weightedtree.min.js delete mode 100644 src/utils/Vizuly/weightedtree/src/core/_namespace.js delete mode 100644 src/utils/Vizuly/weightedtree/src/core/component.js delete mode 100644 src/utils/Vizuly/weightedtree/src/core/util.js delete mode 100644 src/utils/Vizuly/weightedtree/src/svg/filter.js delete mode 100644 src/utils/Vizuly/weightedtree/src/svg/gradient.js delete mode 100644 src/utils/Vizuly/weightedtree/src/svg/text.js delete mode 100644 src/utils/Vizuly/weightedtree/src/theme/column_bar.js delete mode 100644 src/utils/Vizuly/weightedtree/src/theme/corona.js delete mode 100644 src/utils/Vizuly/weightedtree/src/theme/halo.js delete mode 100644 src/utils/Vizuly/weightedtree/src/theme/linearea.js delete mode 100644 src/utils/Vizuly/weightedtree/src/theme/radialprogress.js delete mode 100644 src/utils/Vizuly/weightedtree/src/theme/rangeinput.js delete mode 100644 src/utils/Vizuly/weightedtree/src/theme/scatter.js delete mode 100644 src/utils/Vizuly/weightedtree/src/theme/weightedtree.js delete mode 100644 src/utils/Vizuly/weightedtree/src/ui/rangeinput.js delete mode 100644 src/utils/Vizuly/weightedtree/src/viz/weightedtree.js delete mode 100644 src/utils/Vizuly/weightedtree/styles/examples.css delete mode 100644 src/utils/Vizuly/weightedtree/weightedtree_test.js diff --git a/package.json b/package.json index 6bbd92a..a9b09fe 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "history": "^4.7.2", "jquery": "^3.3.1", "jquery-mousewheel": "^3.1.13", + "katex": "^0.10.1", "query-string": "^6.1.0", "react": "^16.2.0", "react-bootstrap": "^0.32.1", @@ -19,6 +20,7 @@ "react-graph-vis": "^1.0.2", "react-infinite-scroller": "^1.1.4", "react-pdf": "^3.0.5", + "react-quill": "^1.3.3", "react-router-dom": "^4.2.2", "react-scripts": "1.1.1", "reqwest": "^2.0.5", diff --git a/public/index.html b/public/index.html index 98f9b1a..f19150d 100644 --- a/public/index.html +++ b/public/index.html @@ -19,6 +19,8 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> + + 众智化教学平台 diff --git a/server/database/models/mOnlineEditor.js b/server/database/models/mOnlineEditor.js index 1b2c063..145e602 100644 --- a/server/database/models/mOnlineEditor.js +++ b/server/database/models/mOnlineEditor.js @@ -4,10 +4,10 @@ module.exports = { // _id: {type: String, required: true}, // MongoDB 默认生成 _id userId: {type: String, required: true}, type: {type: String, required: true}, - keyword: {type: String, required: true}, - url: {type: String, required: true}, - size: {type: String, required: true}, - description: {type: String, required: true}, + keyword: {type: String}, + url: {type: String}, + size: {type: String}, + description: {type: String}, thumbnailUrl: {type: String, required: true}, uniqueData: {type: Object}, learningTime: {type: String}, @@ -17,6 +17,7 @@ module.exports = { language: {type: String}, applicableObject: {type: Object}, duration: {type: Number}, + quiz: {type: Object}, }, tProject: { _id: {type: String, required: true}, diff --git a/server/public/resource/default-thumbnail.png b/server/public/resource/default-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..4b57611ec484c221a504baf4eb47559d05df562d GIT binary patch literal 9387 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 TeERy|I%q!DdsP0w_Qn4J=LKm= literal 0 HcmV?d00001 diff --git a/server/routes/api/index.js b/server/routes/api/index.js new file mode 100644 index 0000000..cec03dc --- /dev/null +++ b/server/routes/api/index.js @@ -0,0 +1,7 @@ +const knowledgePoints = require('./knowledgePoints'); +const service = require('./service'); + +module.exports = { + knowledgePoints, + service, +}; \ No newline at end of file diff --git a/server/routes/api/knowledgePoints/index.js b/server/routes/api/knowledgePoints/index.js new file mode 100644 index 0000000..4999efa --- /dev/null +++ b/server/routes/api/knowledgePoints/index.js @@ -0,0 +1,104 @@ +const { createInModel, findInModel, findOneInModel } = require('../../../database/model-operations'); +const { pyRequest } = require('../../utils/pyRequest'); +const { getUserInfoFromReq } = require('../../utils/token'); + +async function get(req, res, next) { + const { courseId, knowledgeId } = req.params; + if (!courseId || !knowledgeId) { + return res.sendStatus(400); + } + + try { + const NecessaryInfo = [getUserInfoFromReq(req), findOneInModel('tProject', {_id: courseId})]; + const [user, course] = await Promise.all(NecessaryInfo); + if (course.publishStatus !== 'publish' && course.userId !== user._id) { + return res.sendStatus(401); + } + + if (!Array.isArray(course.data)) return res.sendStatus(500); + let knowledgePoint = course.data.filter(k => k._id === knowledgeId)[0]; + knowledgePoint.course = { + _id: course._id, + title: course.projectName, + }; + + return res.json({ + status: 'success', + data: knowledgePoint, + }); + } catch (error) { + res.sendStatus(500).send({message: error}); + } +} + +async function _getLearningActivities(userId, courseId) { + const docs = await findInModel('tUserActivity', { + userId, + 'activity.action': 'answer-question', + }); + return docs.map(doc => doc.activity).filter(activity => activity.courseId === courseId); +} + +async function _insertLearningActivity(userId, courseId, knowledgeId, correct) { + return createInModel('tUserActivity', { + userId, + activity: { + action: 'answer-question', + courseId, + knowledgeId, + correct, + }, + }); +} + +async function answer(req, res, next) { + const { courseId, knowledgeId } = req.params; + let recommendedKnowledge = null; + + try { + const { correct } = req.body.currentLearning; + const [course, user] = await Promise.all([ + findOneInModel('tProject', { _id: courseId }), + getUserInfoFromReq(req), + ]); + + await _insertLearningActivity(user._id, courseId, knowledgeId, correct); + const activities = await _getLearningActivities(user._id, courseId); + const learningHistory = activities.map(activity => ({ + name: course.data.filter(knowledge => knowledge._id === activity.knowledgeId)[0].title, + correct: activity.correct, + })); + + const recommendedKnowledgeTitle = await pyRequest('/learning-path-recommendation', { + body: { + learningHistory, + course: course.projectName, + } + }, 'POST'); + recommendedKnowledge = course.data.filter(knowledge => knowledge.title === recommendedKnowledgeTitle)[0]; + + if (!recommendedKnowledge) { + return res.status(500).json({ + status: 'error', + message: '找不到推荐知识点!', + }) + } + } catch(err) { + return res.status(500).json({ + status: 'error', + message: err, + }) + } + + return res.json({ + status: 'success', + data: { + recommendedKnowledge, + } + }); +} + +module.exports = { + get, + answer, +}; \ No newline at end of file diff --git a/server/routes/api/service/index.js b/server/routes/api/service/index.js new file mode 100644 index 0000000..6983b86 --- /dev/null +++ b/server/routes/api/service/index.js @@ -0,0 +1,62 @@ +const { getUserInfoFromReq } = require('../../utils/token'); +const { createInModel, findInModel, findOneInModel } = require('../../../database/model-operations'); +const { pyRequest } = require('../../utils/pyRequest'); + + +async function _getLearningActivities(userId, courseId) { + const docs = await findInModel('tUserActivity', { + userId, + 'activity.action': 'answer-question', + }); + return docs.map(doc => doc.activity).filter(activity => activity.courseId === courseId); +} + +async function learningPathRecommendation(req, res, next) { + const { courseId } = req.params; + let recommendedKnowledge = null; + + try { + const [course, user] = await Promise.all([ + findOneInModel('tProject', { _id: courseId }), + getUserInfoFromReq(req), + ]); + + const activities = await _getLearningActivities(user._id, courseId); + const learningHistory = activities.map(activity => ({ + name: course.data.filter(knowledge => knowledge._id === activity.knowledgeId)[0].title, + correct: activity.correct, + })); + + const recommendedKnowledgeTitle = await pyRequest('/learning-path-recommendation', { + body: { + learningHistory, + course: course.projectName, + } + }, 'POST'); + recommendedKnowledge = course.data.filter(knowledge => knowledge.title === recommendedKnowledgeTitle)[0]; + + if (!recommendedKnowledge) { + return res.status(500).json({ + status: 'error', + message: '找不到推荐知识点!', + }) + } + } catch(err) { + return res.status(500).json({ + status: 'error', + message: err, + }) + } + + + return res.json({ + status: 'success', + data: { + recommendedKnowledge, + } + }); +} + +module.exports = { + learningPathRecommendation, +}; \ No newline at end of file diff --git a/server/routes/index.js b/server/routes/index.js index 9ee724b..37e15b9 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -8,6 +8,7 @@ let material = require('./material'); let course = require('./course'); let search = require('./search'); let log = require('./log'); +let api = require('./api'); let tokenObj = require('./utils/token'); @@ -30,13 +31,23 @@ router.post('/deleteProject', tokenObj.checkToken, project.deleteProject); router.post('/getCourse', tokenObj.checkToken, course.getCourse); router.post('/publishCourse', tokenObj.checkToken, course.publishCourse); -/* 课程学习 */ +/* 课程学习(图搜索 API,含图信息) */ router.get('/getCourse', tokenObj.checkToken, course.getCourse); router.get('/getAllCourses', tokenObj.checkToken, course.getAllCourses); router.get('/getKnowledge', tokenObj.checkToken, course.getKnowledge); router.get('/getKunit', tokenObj.checkToken, course.getKunit); router.get('/getMcourse', tokenObj.checkToken, course.getMcourse); router.get('/getAcourse', tokenObj.checkToken, course.getAcourse); +/* 课程学习(数据库检索 API) */ +router.get('/api/v1/courses/:courseId/knowledge-points/:knowledgeId', + tokenObj.checkToken, + api.knowledgePoints.get); +router.post('/api/v1/courses/:courseId/knowledge-points/:knowledgeId/answer', + tokenObj.checkToken, + api.knowledgePoints.answer); +router.get('/api/v1/courses/:courseId/learning-path-recommendation', + tokenObj.checkToken, + api.service.learningPathRecommendation); /* 资源 */ router.post('/upload', tokenObj.checkToken, material.uploadMaterial); diff --git a/server/routes/material/uploadMaterial.js b/server/routes/material/uploadMaterial.js index 7a7957f..8859d18 100644 --- a/server/routes/material/uploadMaterial.js +++ b/server/routes/material/uploadMaterial.js @@ -7,6 +7,10 @@ const formatParser = require('../utils/format-parser'); const updateGraph = require('../utils/updateGraph'); const { createInModel, findOneInModel } = require('../../database/model-operations'); +const DEFAULT_THUMBNAIL = { + default: '/resource/default-thumbnail.png', +}; + /** * 上传文件并返回文件相关属性 * @param file @@ -115,6 +119,7 @@ const getInfoFromReq = function (req) { description, keyword, language, + quiz, } = req.body; let materialInfo = { @@ -124,6 +129,8 @@ const getInfoFromReq = function (req) { description, keyword, language, + quiz, + thumbnailUrl: DEFAULT_THUMBNAIL.default, }; let files = []; @@ -141,7 +148,7 @@ const getInfoFromReq = function (req) { .then(fileInfo => Object.assign(materialInfo, fileInfo)) } else if (files.length === 0) { - return Promise.reject('请上传文件!'); + return Promise.resolve(materialInfo); } else { return Promise.reject('一次只能上传一个文件!'); @@ -153,51 +160,30 @@ const uploadMaterial = function (req, res, next) { let materialInfo = {}; // 成功返回素材信息 - const onSuccess = data => - res.json({ - material: { - duration: data.duration, - source: data.url, - thumbnail: data.thumbnailUrl, - title: data.name, - type: data.type, - id: data._id, - keyword: data.keyword, - size: data.size, - description: data.description - } + const onSuccess = material => { + return res.json({ + material, }); - // res.json({ - // status: 'success', - // data - // }); + }; // 失败返回错误信息 const onError = err => { - console.log(err); + console.error(err); res.json({ status: 'error', message: err.toString(), }); }; - const uploadToDatabaseAndGraph= function() { - const pUploadToDatabase = createInModel('tMaterial', materialInfo); - const pUploadToGraph = Promise.resolve(updateGraph(materialInfo, 'material')); - - return Promise.all([pUploadToDatabase, pUploadToGraph]); - }; - - const getUserId = function(username) { + const getUser = function(username) { return findOneInModel('tUser', { name: username }); }; getInfoFromReq(req) - .then(info => { materialInfo = info; return getUserId(username); }) + .then(info => { materialInfo = info; return getUser(username); }) .then(user => { materialInfo.userId = user._id; }) - .then(uploadToDatabaseAndGraph) - .then(([resOfDatabase, resOfGraph]) => resOfDatabase) // 一次只上传一个 Material - .then(onSuccess) + .then(() => createInModel('tMaterial', materialInfo)) + .then(data => onSuccess(data._doc)) .catch(onError); }; diff --git a/server/routes/utils/pyRequest.js b/server/routes/utils/pyRequest.js new file mode 100644 index 0000000..1e6786d --- /dev/null +++ b/server/routes/utils/pyRequest.js @@ -0,0 +1,55 @@ +const request = require('request'); +const config = require('config'); + +const NO_GRAPH_SERVER = config.get('debug') && config.get('debugConfig').noGraphServer; +const PY_HOST = config.get('graphServer'); + + +const _urlWithParams = (url, query) => { + let queryArr = Object.keys(query).map(key => key + '=' + query[key]); + return url + '?' + queryArr.join('&'); +}; +const pyRequest = function (url, options, method) { + if (NO_GRAPH_SERVER) return Promise.reject('No pyServer! Check the config.'); + + options = options || {}; + options.method = method || options.method || 'GET'; // 传入的 method 优先 + if (!options.headers || !options.headers['Content-Type']) { + options.headers = Object.assign({ + "Content-Type": "application/json", + "charset": "utf-8", + }, options.headers); + } + url = PY_HOST + url; + if (options.query && Object.keys(options.query).length !== 0) { + url= _urlWithParams(url, options.query); + } + options.body = Object.prototype.toString.call(options.body) === '[object Object]' + ? JSON.stringify(options.body): options.body; + if (options.method === 'GET' && options.body) { reject('GET 请求无法接收 body 参数'); } + + + return new Promise((resolve, reject) => { + request(url, options, function (err, res) { + if (err) { return reject(err); } + + let body = {}; + try { + body = JSON.parse(res.body); + } + catch(err) { + return reject(err); + } + + if (body.status === 'success') { + return resolve(body.result); + } + + return reject(body.result) + }); + }); +}; + +module.exports = { + pyRequest +}; \ No newline at end of file diff --git a/src/course-manage/CourseContent.js b/src/course-manage/CourseContent.js index 685907c..b6f0c40 100644 --- a/src/course-manage/CourseContent.js +++ b/src/course-manage/CourseContent.js @@ -1,15 +1,14 @@ import React, { Component } from 'react'; import Graph from 'react-graph-vis'; -import { VizulyWeightedTree } from '../utils/Vizuly'; -import { Layout, message } from 'antd'; +import {Button, Layout, message} from 'antd'; +import { Link, Redirect } from 'react-router-dom'; import request from '../utils/netService/request'; import logger from '../utils/logger'; import graphParser from '../utils/graphParser'; +import netService from '../utils/netService'; import './index.css' -import KnowledgePreview from "./knowledgePreview"; -const { Content, Footer } = Layout; const NODE_RECOMMEND_THRESHOLD = 50; const PATH_RECOMMEND_THRESHOLD = 50; @@ -19,7 +18,7 @@ class CourseContent extends Component { super(props); this.state = { course: null, - currentCourse: null, + knowledgeId: null, graphConfig: { physics: false, layout: { @@ -38,24 +37,12 @@ class CourseContent extends Component { useBorderWithImage: true } } - } + }, + recommendedKnowledge: null, }; - this.onSetCurrentCourse = this.onSetCurrentCourse.bind(this); - this.onPrevCourse = this.onPrevCourse.bind(this); - this.onNextCourse = this.onNextCourse.bind(this); - this.onShowKnowledgePreview = this.onShowKnowledgePreview.bind(this); - this.onHideKnowledgePreview = this.onHideKnowledgePreview.bind(this); this.courseToData = this.courseToData.bind(this); }; - onSetCurrentCourse(course) { - console.log('setCurrentCourse', course); - logger.log(logger.START_LEARNING, { - course, - }); - this.setState({ currentCourse: course }); - }; - courseToData(course) { if(!course||!course.data){ return null @@ -90,80 +77,65 @@ class CourseContent extends Component { }); }; - getData = (data, callback) => { + getData = (courseId) => { const token = localStorage.getItem('token'); + request.get('/getCourse', { headers: { "Content-Type": "application/json", "Authorization": token, }, query: { - id: this.props.projectId + id: courseId, }, }).then(res => res.project) .then(this.pSetCourse) .then(this.props.updateCurrentLesson) .catch(err => console.log(err)); - }; + netService.recommendKnowledge(courseId) + .then(data => { + this.setState({ + recommendedKnowledge: data.recommendedKnowledge, + }); + }); - parseCourse = course => graphParser.parseLesson(course, { recommendation: true }); - - _getCourseDataById = id => { - const courseDatas = this.state.course.data; - if (!Array.isArray(courseDatas) || courseDatas.length <= 0) return; - - return courseDatas.filter(courseData => courseData._id === id)[0]; }; - onShowKnowledgePreview = () => { - this.setState({ showPreview: true }); - }; + parseCourse = course => graphParser.parseLesson(course, { recommendation: true }); - onHideKnowledgePreview = () => { - this.setState({ showPreview: false }); - }; + handleNodeClick = node => { - onPrevCourse = id => { - const currentCourse = this.state.currentCourse; - console.log('currentCourse: ', currentCourse); - if (!currentCourse || !currentCourse.hasPrevNode) return; + if (!node || !Array.isArray(node.nodes) || node.nodes.length < 1) return; + const knowledgeId = node.nodes[0]; - const prevCourse = this._getCourseDataById(currentCourse.hasPrevNode[0]); - console.log('prevCourse: ', prevCourse); - if (prevCourse) { - this.onSetCurrentCourse(prevCourse); - } + this.setState({ + knowledgeId, + }); }; - onNextCourse = id => { - const currentCourse = this.state.currentCourse; - if (!currentCourse || !currentCourse.hasNextNode) return; + componentDidMount() { + const courseId = this.props.match.params.courseId; - const nextCourse = this._getCourseDataById(currentCourse.hasNextNode[0]); - if (nextCourse) { - this.onSetCurrentCourse(nextCourse); + if (courseId) { + this.getData(courseId); } - }; - - handleNodeClick = node => { + } - if (!node) return; + render() { + const courseId = this.props.match.params.courseId; + const { knowledgeId } = this.state; - const currentCourse = this.state.course.data.filter(courseData => courseData._id === node.nodes[0])[0]; - this.onSetCurrentCourse(currentCourse); - this.onShowKnowledgePreview(); + if (!courseId) return (); - console.log('clicked node', node); - }; + if (knowledgeId) { + return ( + + ); + } - componentDidMount() { - this.getData(); - } - render() { let graphData = this.parseCourse(this.state.course); - console.log(graphData); const GRAPH_JSX = ( ); - const TREE_JSX = ( - - ); return (
this.container=dom} - style={{width: '100%', height: '100%'}} + style={{ + width: '100%', + height: '100%', + margin: '20px', + }} > - - - { graphData ? GRAPH_JSX :

等待载入课程...

} - { this.state.showPreview && } -
-
-

- 红色结点:当前学习课程/推荐学习课程;
- 红色箭头:推荐路径 -

-
-
+
+ { graphData ? GRAPH_JSX :

等待载入课程...

} +
+
+

课程名:{this.state.course && this.state.course.projectName}

+

+ 推荐学习路径:{this.state.recommendedKnowledge && this.state.recommendedKnowledge.title} + + + +

+
); } @@ -219,6 +188,6 @@ const NoCurrentLesson = () => { export { NoCurrentLesson, - CourseContent + CourseContent, } export default CourseContent; \ No newline at end of file diff --git a/src/course-manage/CourseItem.js b/src/course-manage/CourseItem.js index 520d7e8..fd9fd9e 100644 --- a/src/course-manage/CourseItem.js +++ b/src/course-manage/CourseItem.js @@ -10,7 +10,6 @@ const DEFAULT_IMG = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22 const CourseItem = (props) => { const {thumbnailUrl, title, description, userName, publishDate, lessonId} = props; - console.log(props) return (
{
教师:{userName}
发布时间:{publishDate}
- ) -} - - -const TeachInfoArea = (props) => { - const {teachInfo, onNextCourse, onPrevCourse, kUnitId} = props; - return ( -
- -

教学单元名称:{teachInfo.title}

-

教学单元描述:{teachInfo.description}

-

教学单元关键字:{teachInfo.keyword}

- - -
-
- ) -} - -class PDFViewer extends Component { - state = { - numPages: null, - pageNumber: 1, - } - onDocumentLoad = ({numPages}) => { - this.setState({numPages}); - } - - - prevPage = () => { - this.setState(prev => { - let newPage = prev.pageNumber <= 1 ? 1 : prev.pageNumber - 1; - return {pageNumber: newPage}; - }); - }; - - nextPage = () => { - this.setState(prev => { - let newPage = prev.pageNumber >= prev.numPages ? prev.numPages : prev.pageNumber + 1; - return {pageNumber: newPage}; - }); - }; - - closePage = () => { - ReactDOM.unmountComponentAtNode(document.getElementById('pdfView')); - } - - render() { - const {pageNumber, numPages} = this.state; - const url = this.props.url; - return ( -
- - - - alert('oh')} - file={DEFAULT_PDF || url} - onLoadSuccess={this.onDocumentLoad} - > - - -

Page {pageNumber} of {numPages}

-
- ); - } -} - - -export default KnowledgePreview diff --git a/src/course-manage/knowledgePreviewPage.js b/src/course-manage/knowledgePreviewPage.js new file mode 100644 index 0000000..3cf46b3 --- /dev/null +++ b/src/course-manage/knowledgePreviewPage.js @@ -0,0 +1,375 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import InfiniteScroll from 'react-infinite-scroller'; + +import { Layout, Breadcrumb, Row, Col, List, Card, Button, Timeline, Icon, Radio } from 'antd'; +import { Link, withRouter } from 'react-router-dom'; + +import netService from '../utils/netService'; +import { PDFMaterialDisplay, ImageMaterialDisplay, VideoMaterialDisplay, QuizMaterialDisplay } from "./materialDisplayComponents"; + + +const DEFAULT_MATERIAL = { + 'img': 'https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2486531409,3348270894&fm=27&gp=0.jpg', + 'video': 'http://cuc-richmedia.oss-cn-beijing.aliyuncs.com/media/new_aliyun0731/P5kn52ss5n.mp4?butteruid=1527400149022' +}; +const TYPE_CONVERSE = { + "视频": "video", + "图片": "image", + 'high': "高", + 'veryhigh': '很高', + 'middle': '中', + 'low': '低', + 'verylow': '很低', + 'active': '主动型', + 'commentary': '解说型', + 'mixing': '混合型', + 'undefined': '未定义', +}; + + +class KnowledgePreviewPage extends React.Component { + state = { + course: null, + knowledge: null, + kUnit: null, + mCourse: null, + aCourses: null, + }; + + componentDidMount() { + const { courseId, knowledgeId } = this.props.match.params; + this.refreshState(courseId, knowledgeId); + } + + componentWillReceiveProps(nextProps, nextContext) { + const { courseId, knowledgeId } = nextProps.match.params; + this.refreshState(courseId, knowledgeId); + } + + refreshState(courseId, knowledgeId) { + netService.getKnowledgeV1(courseId, knowledgeId) + .then(knowledge => { + const { + course, + teachUnit: kUnit, + } = knowledge; + const { + mCourseUnit: mCourse, + aCourseUnit: aCourses, + } = kUnit; + + if (courseId !== course._id) throw Error(`获取到的课程信息与 URL 中的不同!URL: ${courseId}, 获取: ${course._id}`); + + this.setState({ + course, + knowledge, + kUnit, + mCourse, + aCourses, + }); + }) + .catch(err => console.error(err)); + } + + render() { + const { courseId, knowledgeId } = this.props.match.params; + if (!courseId) return
课程不存在!
; + if (!knowledgeId) return
知识点不存在!
; + + const { course, knowledge, kUnit, mCourse, aCourses } = this.state; + + if (!course || !knowledge) return
获取相关信息中...
; + if (!course.title || !knowledge.title) return
获取信息错误!
; + + return ( + + + + + { course.title } + + + + { knowledge.title } + + + + + ); + } +} + +class KnowledgePreview extends React.Component { + state = { + currentCourseUnit: this.props.mCourse || {}, + recommendationKnowledge: null, + }; + + changeCourseUnit = (courseUnit) => { + this.setState({ + currentCourseUnit: courseUnit, + }); + }; + + afterLearning = (correct) => { + const { course, knowledge } = this.props; + + return netService.answerKnowledge(course._id, knowledge._id, correct) + .then(data => { + this.setState({ + recommendationKnowledge: data.recommendedKnowledge, + }); + }) + .catch(err => { + console.error(err); + }); + }; + + refreshState(courseId) { + return netService.recommendKnowledge(courseId) + .then(data => { + this.setState({ + recommendationKnowledge: data.recommendedKnowledge, + }); + }); + } + + componentDidMount() { + const { course } = this.props; + this.refreshState(course._id); + } + + componentWillReceiveProps(nextProps, nextContext) { + const { course } = nextProps; + this.refreshState(course._id); + } + + render() { + const { course, knowledge, kUnit, mCourse, aCourses } = this.props; + const { currentCourseUnit, recommendationKnowledge } = this.state; + + if (!kUnit) return null; + + return ( +
+
+ + +
+ +
+ + + + +
+ + + + + + + + + +
+
+ ) + } +} + +class DisplayArea extends React.Component { + render() { + const { displayMaterial, afterLearning } = this.props; + + const emptyView = ( +
+ 暂无可显示资源 +
+ ); + + if (!displayMaterial) return emptyView; + + switch(displayMaterial.type) { + case '图片': return ; + case '视频': return ; + case '课件': return ; + case '试题': return ; + case '音频': + case '动画': + case '文本': + default: return emptyView; + } + } +} + +const SubCourseList = ({ mCourse, aCourses, changeCourseUnit}) => ( +
+ + +
+); + +class AidList extends React.Component { + render() { + const { aCourses, changeCourseUnit } = this.props; + + return ( +
+ 辅课时信息
} + itemLayout="vertical " + dataSource={aCourses} + renderItem={item => ( + changeCourseUnit(item)}> + 播放]} + > + + + )} + /> + + ) + } + +}; + +const KnowledgeInfoArea = ({knowledgeInfo}) => { + return ( +
+ +

知识点名称:{knowledgeInfo.title}

+

大纲要求难度:{knowledgeInfo.demand}

+

学生掌握程度:{knowledgeInfo.achieve}

+
+
+ ) +}; + +const MainLessonInfoArea = ({mainCourseInfo, changeCourseUnit}) => { + const { + title, + difficulty, + interactionDegree, + interactionType, + material, + } = mainCourseInfo; + + return ( +
+ changeCourseUnit(mainCourseInfo)} + key='play'> + 播放
]}> +

主课时名称:{title}

+

主课时难度:{TYPE_CONVERSE[difficulty]}

+

主课时交互程度:{TYPE_CONVERSE[interactionDegree]}

+

主课时交互类型:{TYPE_CONVERSE[interactionType]}

+ + + ) +}; + +const KnowledgeUnitArea = ({ teachInfo }) => { + return ( +
+ +

教学单元名称:{teachInfo.title}

+

教学单元描述:{teachInfo.description}

+

教学单元关键字:{teachInfo.keyword}

+
+
+ ) +}; + +class LearningPathRecommendationAreaWithoutRouter extends React.Component { + render () { + let { currentKnowledge, recommendationKnowledge } = this.props; + if (!currentKnowledge) { + return ( + +
暂无相关资料
+
+ ); + } + if (!recommendationKnowledge) { + recommendationKnowledge = currentKnowledge; + } + + const linkKnowledgeId = recommendationKnowledge && recommendationKnowledge._id? recommendationKnowledge._id: currentKnowledge._id; + const currentUrl = this.props.match.url; + const currentKnowledgeId = this.props.match.params.knowledgeId; + const linkUrl = currentUrl.replace(currentKnowledgeId, linkKnowledgeId); + + return ( + 跳转到推荐知识点) ]} + > + + + + + { currentKnowledge.title } + { recommendationKnowledge.title } + + + + + + ); + }; +} +const LearningPathRecommendationArea = withRouter(LearningPathRecommendationAreaWithoutRouter); + + +export default KnowledgePreviewPage diff --git a/src/course-manage/materialDisplayComponents.js b/src/course-manage/materialDisplayComponents.js new file mode 100644 index 0000000..64a3a85 --- /dev/null +++ b/src/course-manage/materialDisplayComponents.js @@ -0,0 +1,296 @@ +import React from 'react'; +import { Document, Page } from 'react-pdf'; +import { Button, Radio, Checkbox, Row, Col, Form } from 'antd'; +import RichTextEditor from "../publicComponents/RichTextEditor"; + + +class PDFMaterialDisplay extends React.Component { + state = { + numPages: null, + pageNumber: 1, + }; + + DEFAULT_PDF = '../searchManager/test.pdf'; + + onDocumentLoad = ({numPages}) => { + this.setState({numPages}); + }; + + prevPage = () => { + this.setState(prev => { + let newPage = prev.pageNumber <= 1 ? 1 : prev.pageNumber - 1; + return {pageNumber: newPage}; + }); + }; + + nextPage = () => { + this.setState(prev => { + let newPage = prev.pageNumber >= prev.numPages ? prev.numPages : prev.pageNumber + 1; + return {pageNumber: newPage}; + }); + }; + + render() { + const {pageNumber, numPages} = this.state; + const url = this.props.url; + return ( +
+ + + + + +

Page {pageNumber} of {numPages}

+
+ ); + } +} + +const ImageMaterialDisplay = ({ url }) => ( + +); + +const VideoMaterialDisplay = ({ url }) => ( +