Przeglądaj źródła

feat: 为课程添加知识点

ChenYangfan 6 lat temu
rodzic
commit
6dd446a803

+ 19 - 18
src/common-style.scss

@@ -33,6 +33,7 @@
 }
 
 .vertical-center {
+  box-sizing: border-box;
   display: flex;
   align-items: center;
 }
@@ -59,73 +60,73 @@
 }
 
 .mb-8 {
-  margin-bottom: 8px;
+  margin-bottom: 8px !important;
 }
 
 .mb-12 {
-  margin-bottom: 12px;
+  margin-bottom: 12px !important;
 }
 
 .mb-16 {
-  margin-bottom: 16px;
+  margin-bottom: 16px !important;
 }
 
 .mb-24 {
-  margin-bottom: 24px;
+  margin-bottom: 24px !important;
 }
 
 .mb-32 {
-  margin-bottom: 32px;
+  margin-bottom: 32px !important;
 }
 
 .mt-6 {
-  margin-top: 6px;
+  margin-top: 6px !important;
 }
 
 .mt-8 {
-  margin-top: 8px;
+  margin-top: 8px !important;
 }
 
 .mt-16 {
-  margin-top: 16px;
+  margin-top: 16px !important;
 }
 
 .mt-24 {
-  margin-top: 24px;
+  margin-top: 24px !important;
 }
 
 .mt-32 {
-  margin-top: 32px;
+  margin-top: 32px !important;
 }
 
 .mr-2 {
-   margin-right: 2px;
+   margin-right: 2px !important;
  }
 
 .mr-4 {
-  margin-right: 4px;
+  margin-right: 4px !important;
 }
 
 .mr-8 {
-  margin-right: 8px;
+  margin-right: 8px !important;
 }
 
 .mr-16 {
-  margin-right: 16px;
+  margin-right: 16px !important;
 }
 
 .ml-2 {
-  margin-left: 2px;
+  margin-left: 2px !important;
 }
 
 .ml-4 {
-  margin-left: 4px;
+  margin-left: 4px !important;
 }
 
 .ml-8 {
-  margin-left: 8px;
+  margin-left: 8px !important;
 }
 
 .ml-16 {
-  margin-left: 16px;
+  margin-left: 16px !important;
 }

+ 1 - 1
src/router/quiz.js

@@ -50,7 +50,7 @@ export default [{
   component: () => import(/* webpackChunkName: "student-quiz" */ '@/views/quiz/StudentQuestions.vue')
 }, {
   meta: { roles: ['student'] },
-  path: '/student/recommend',
+  path: '/student/recommend/:knowledgeId',
   name: 'do-recommend-question',
   component: () => import(/* webpackChunkName: "student-quiz" */ '@/views/quiz/DoRecommendQuestion.vue')
 }

+ 6 - 0
src/router/teacher.js

@@ -61,6 +61,12 @@ export default [{
       path: 'teacher-edit-notice/:noticeId/:courseId',
       name: 'teacher-edit-notice',
       component: () => import(/* webpackChunkName: "teacher" */ '@/views/teacher/components/EditNotice.vue')
+    }, {
+      // 教师设置课程的默认知识点
+      meta: { roles: ['teacher'] },
+      path: 'edit-course-knowledge/:courseId',
+      name: 'teacher-edit-course-knowledge',
+      component: () => import(/* webpackChunkName: "teacher" */ '@/views/teacher/components/SelectCourseKnowledge.vue')
     }]
   }, {
     // 教师讨论区页面

+ 6 - 0
src/server/course.js

@@ -37,6 +37,12 @@ export function modifyCourse ({ id, name, bio, code }) {
     .then(res => res.data)
 }
 
+// 修改课程知识点
+export function modifyCourseKnowledgeId ({ courseId, knowledgeId }) {
+  return axios
+    .put(useCourseServer(`/${courseId}/knowledge`), { knowledgeId })
+}
+
 // 学生选课
 export function chooseCourse ({ courseId, code }) {
   const data = { code }

+ 25 - 21
src/views/quiz/DoRecommendQuestion.vue

@@ -27,7 +27,8 @@
             <p class="mb-4" v-if="question.tag">标签:{{question.tag.join(',')}}</p>
           </div>
           <show-transition>
-            <div v-if="submitted" class="answer" :class="question.studentAnswer.answer === question.answer ? 'b-green' : 'b-red'">
+            <div v-if="submitted" class="answer"
+                 :class="question.studentAnswer.answer === question.answer ? 'b-green' : 'b-red'">
               <p class="mb-8">
                 答案:{{question.answer}}
               </p>
@@ -65,7 +66,7 @@ export default {
       knowledgeTree: null,
       knowledgeNaming: null,
       interactiveMap: null,
-      selectedKnowledgeIdList: [],
+      selectedKnowledgeIdList: this.$route.params.knowledgeId !== ' ' ? this.$route.params.knowledgeId.split(',') : [],
       submitted: false,
 
       // 做题起止时间相关,时间为了方便加减采用毫秒表示
@@ -75,7 +76,7 @@ export default {
       records: {}
     }
   },
-  mounted () {
+  created () {
     getKnowledgeNodes()
       .then(res => {
         this.knowledgeNaming = {}
@@ -91,24 +92,26 @@ export default {
     getQuestions () {
       this.questions = null
       this.interactiveMap = null
-      getRecommendationQuestions({ questionNum: 5, knowledgeId: this.selectedKnowledgeIdList })
-        .then(res => {
-          this.questions = res.map(question => ({
-            ...question,
-            studentAnswer: { answer: '' }
-          }))
-          getInteractiveList({ questionIdJoinByComma: this.questions.map(_ => _.id).join(',') })
-            .then(res => {
-              if (res) {
-                const tempMap = {}
-                res.forEach(item => {
-                  tempMap[item.questionId] = { marked: item.collection, rate: item.rate }
-                })
-                this.interactiveMap = tempMap
-              }
-            })
-            .catch(err => this.$setErrorMessage(err.response.data.msg))
-        })
+      getRecommendationQuestions({
+        questionNum: 5,
+        knowledgeId: this.selectedKnowledgeIdList
+      }).then(res => {
+        this.questions = res.map(question => ({
+          ...question,
+          studentAnswer: { answer: '' }
+        }))
+        getInteractiveList({ questionIdJoinByComma: this.questions.map(_ => _.id).join(',') })
+          .then(res => {
+            if (res) {
+              const tempMap = {}
+              res.forEach(item => {
+                tempMap[item.questionId] = { marked: item.collection, rate: item.rate }
+              })
+              this.interactiveMap = tempMap
+            }
+          })
+          .catch(err => this.$setErrorMessage(err.response.data.msg))
+      })
         .catch(err => this.$setErrorMessage(err.response.data.msg))
     },
     handleChoice: getHandleChoice({ that: this }),
@@ -168,6 +171,7 @@ export default {
 
 <style lang="scss" scoped>
 @import "~@/style/theme.scss";
+
 .do-recommend-question {
   padding: 64px 24px;
   max-width: 800px;

+ 23 - 15
src/views/student/components/StudentCourseDetail.vue

@@ -7,17 +7,25 @@
         {{showMore ? '收起' : '显示'}}详情
       </c-button>
     </back-title>
-    <div class="course-name-bar">{{course.name}}</div>
+    <div class="course-name-bar vertical-center">
+      课程:{{course.name}}
+      <c-button icon="close" theme="red" :left="8" text @click="handleQuitCourse">退出课程</c-button>
+    </div>
     <show-transition>
       <course-detail v-if="showMore" :course="course"></course-detail>
     </show-transition>
-    <template v-for="notice of notices">
-      <notice-card show :key="'notice-' + notice.id"
+    课程公告
+    <template v-for="(notice, index) of notices">
+      <notice-card :show="index === 0" :key="'notice-' + notice.id"
                    :notice="notice">
       </notice-card>
     </template>
-    <c-button icon="list" theme="blue" block class="mt-24 mb-16" @click="handleCheckSlideList">课件列表</c-button>
-    <c-button icon="close" theme="red" block @click="handleQuitCourse">退出课程</c-button>
+    <router-link :to="{ name: 'do-recommend-question', params: { knowledgeId: course.knowledgeId ? course.knowledgeId.join(',') : ' '} }">
+      <c-button standout icon="highlight" theme="green" block class="mt-24 mb-16">基于本课程知识点刷题</c-button>
+    </router-link>
+    <router-link :to="{ name: 'student-course-slides' }">
+      <c-button no-bold icon="list" theme="blue" block class="mt-8">课件列表</c-button>
+    </router-link>
   </tab-side-component>
 </template>
 
@@ -25,8 +33,7 @@
 import TabSideComponent from '@/components/Layouts/TabSideComponent'
 import CButton from '@/components/Basic/CButton'
 import BackTitle from '@/components/Basic/BackTitle'
-import { mapState } from 'vuex'
-import { quitCourse } from '@/server/course'
+import { getOneCourse, quitCourse } from '@/server/course'
 import CourseDetail from '@/components/Details/CourseDetail'
 import { RESET_COURSE } from '@/store/types/main-types'
 import momentFromNow from '@/utils/moment-from-now'
@@ -40,13 +47,11 @@ export default {
     return {
       newCode: '',
       notices: null,
-      showMore: false
+      showMore: false,
+      course: {}
     }
   },
   computed: {
-    ...mapState({
-      course: state => state.main.courseInfo
-    }),
     createTime () {
       return momentFromNow(this.course.createAt)
     }
@@ -56,7 +61,13 @@ export default {
       this.fetchNotices()
     }
   },
-  mounted () {
+  created () {
+    getOneCourse({ courseId: this.$route.params.courseId })
+      .then(res => {
+        this.course = res
+        this.fetchNotices()
+      })
+      .catch(err => this.$setErrorMessage(err.response.data.msg))
     this.fetchNotices()
   },
   methods: {
@@ -67,9 +78,6 @@ export default {
         this.$setErrorMessage(err.response.data.msg)
       })
     },
-    handleCheckSlideList () {
-      this.$router.push({ name: 'student-course-slides' })
-    },
     handleQuitCourse () {
       const msg = '确定退出本课程吗:\n\n' + this.course.name
       if (confirm(msg)) {

+ 1 - 1
src/views/student/tabs/StudentQuiz.vue

@@ -6,7 +6,7 @@
         <router-link :to="{ name: 'student-question-table' }">
           <c-button icon="assignment" :left="12" theme="blue" text>题库</c-button>
         </router-link>
-        <router-link :to="{ name: 'do-recommend-question' }">
+        <router-link :to="{ name: 'do-recommend-question', params: { knowledgeId: ' ' } }">
           <c-button icon="highlight" theme="orange" text>刷题</c-button>
         </router-link>
       </h1>

+ 3 - 3
src/views/teacher/components/EditNotice.vue

@@ -1,9 +1,9 @@
 <template>
   <tab-side-component>
     <back-title>{{noticeId === 0 || isEmpty(noticeId) ? '新建' : '编辑' }}课程公告</back-title>
-    <div class="sub-title">公告标题</div>
+    <div class="mb-12">公告标题</div>
     <c-input v-model="title" placeholder="标题长度上限为 50 个字,请保持简短" :maxlength="50" class="mb-16"></c-input>
-    <div class="sub-title">公告内容</div>
+    <div class="mb-12">公告内容</div>
     <textarea v-model="content" placeholder="内容长度上限为 1000 字" :maxlength="1000" class="mb-16"></textarea>
     <c-button theme="green" standout icon="check" @click="handleSubmit">确认提交</c-button>
     <c-button v-if="noticeId !== 0 && !isEmpty(noticeId)" :left="8" theme="red" icon="close" @click="handleDelete">删除公告</c-button>
@@ -82,7 +82,7 @@ textarea {
   border: none;
   outline: none;
   width: 100%;
-  min-height: 260px;
+  min-height: 220px;
   height: 100%;
   line-height: 1.5;
   padding: 0 12px 0 12px;

+ 85 - 0
src/views/teacher/components/SelectCourseKnowledge.vue

@@ -0,0 +1,85 @@
+<template>
+  <tab-side-component>
+    <back-title>
+      设置课程知识点
+    </back-title>
+    <div class="course-name-bar">课程名:{{course ? course.name : ''}}</div>
+    <div class="sub-title mt-16">知识点设置</div>
+    <treeselect
+      class="mt-16"
+      v-if="knowledgeTree"
+      v-model="selectedKnowledgeIdList"
+      :multiple="true"
+      :options="knowledgeTree ? knowledgeTree : null"
+      placeholder="选择相关知识点"
+    />
+    <div v-else class="mt-24">知识点加载中...</div>
+    <c-button standout icon="check" theme="green" class="mt-32" @click="handleChange">更改知识点</c-button>
+    <div class="tip mt-16">
+      * 为课程设置知识点后,学生可以选择具体课程让系统为其推荐题目,所推荐的题目将与所设置的知识点有关。
+    </div>
+  </tab-side-component>
+</template>
+
+<script>
+import TabSideComponent from '@/components/Layouts/TabSideComponent'
+import BackTitle from '@/components/Basic/BackTitle'
+import { getOneCourse, modifyCourseKnowledgeId } from '@/server/course'
+import Treeselect from '@riophae/vue-treeselect'
+import '@riophae/vue-treeselect/dist/vue-treeselect.css'
+import { constructTree } from '@/utils/construct-tree'
+import { getKnowledgeNodes } from '@/server/knowledge'
+import CButton from '@/components/Basic/CButton'
+
+export default {
+  name: 'SelectCourseKnowledge',
+  components: { CButton, Treeselect, BackTitle, TabSideComponent },
+  data () {
+    return {
+      course: null,
+      knowledgeTree: null,
+      selectedKnowledgeIdList: []
+    }
+  },
+  mounted () {
+    this.getCourse()
+    this.getKnowledgeNodes()
+  },
+  methods: {
+    getCourse () {
+      getOneCourse({ courseId: this.$route.params.courseId })
+        .then(res => {
+          this.course = res
+          this.selectedKnowledgeIdList = this.course.knowledgeId
+        })
+        .catch(err => this.$setErrorMessage(err.response.data.msg))
+    },
+    getKnowledgeNodes () {
+      getKnowledgeNodes()
+        .then(res => {
+          this.knowledgeTree = constructTree(res)
+        })
+        .catch(err => this.$setErrorMessage(err.response.data.msg))
+    },
+    handleChange () {
+      modifyCourseKnowledgeId({ courseId: this.course.id, knowledgeId: this.selectedKnowledgeIdList })
+        .then(res => {
+          this.$setSuccessMessage('更新成功')
+          this.getCourse()
+        })
+        .catch(err => this.$setErrorMessage(err.response.data.msg))
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.course-name-bar {
+  color: #5a6a7a;
+}
+
+.tip {
+  color: #aabaca;
+  font-size: 12px;
+}
+</style>

+ 34 - 22
src/views/teacher/components/TeacherCourseDetail.vue

@@ -10,23 +10,32 @@
     <show-transition>
       <course-detail v-if="showMore" :course="course"></course-detail>
     </show-transition>
-    <template v-if="notices">
+    <div class="vertical-center mb-24">
+      课程:{{course.name}}
+      <c-button text icon="close" :left="12" theme="red" @click="handleDeleteCourse">删除课程</c-button>
+    </div>
+    <div>
       <template v-for="notice of notices">
-        <notice-card show can-edit :notice="notice" :key="'notice-' + notice.id"></notice-card>
+        <notice-card can-edit :notice="notice" :key="'notice-' + notice.id"></notice-card>
       </template>
-    </template>
-    <div class="vertical-center mt-24">
-      <c-button icon="list" theme="blue" block @click="handleCheckSlideList">课件列表</c-button>
-      <router-link :to="{ name: 'teacher-edit-notice', params: { noticeId: 0, courseId: course.id } }">
-        <c-button theme="green" :left="12" icon="add">新建课程公告</c-button>
+    </div>
+    <router-link :to="{ name: 'teacher-edit-notice', params: { noticeId: 0, courseId: course.id } }">
+      <c-button text :left="-12" theme="green" icon="add">新建课程公告</c-button>
+    </router-link>
+    <div class="vertical-center mt-32">
+      <router-link :to="{ name: 'teacher-edit-course-knowledge', params: { courseId: course.id } }">
+        <c-button icon="lightbulb_outline" theme="green" block>设置课程知识点</c-button>
+      </router-link>
+      <router-link :to="{ name: 'teacher-course-slides' }">
+
       </router-link>
+      <c-button icon="list" theme="blue" block :left="12" no-bold>课件列表</c-button>
     </div>
-    <div class="sub-title mt-24">修改选课码:</div>
+    <div class="mt-32 mb-12">修改选课码</div>
     <div class="change-wrapper mb-24">
       <c-input placeholder="修改不会影响已选学生" v-model="newCode"/>
       <c-button text theme="green" icon="check">确认</c-button>
     </div>
-    <c-button icon="delete" class="mt-32" theme="red" block @click="handleDeleteCourse">删除课程</c-button>
   </tab-side-component>
 </template>
 
@@ -35,8 +44,7 @@ import TabSideComponent from '@/components/Layouts/TabSideComponent'
 import CInput from '@/components/Basic/CInput'
 import CButton from '@/components/Basic/CButton'
 import BackTitle from '@/components/Basic/BackTitle'
-import { mapState } from 'vuex'
-import { modifyCourse, removeCourse } from '@/server/course'
+import { getOneCourse, modifyCourse, removeCourse } from '@/server/course'
 import CourseDetail from '@/components/Details/CourseDetail'
 import momentFromNow from '@/utils/moment-from-now'
 import ShowTransition from '@/animations/ShowTransition'
@@ -50,34 +58,37 @@ export default {
       newCode: '',
       notices: null,
       showMore: false,
-      showNoticeForm: false
+      showNoticeForm: false,
+      course: {}
     }
   },
   computed: {
-    ...mapState({
-      course: state => state.main.courseInfo
-    }),
     createTime () {
       return momentFromNow(this.course.createAt)
     }
   },
   watch: {
-    course () {
-      this.fetchNotices()
+    $route () {
+      this.init()
     }
   },
-  mounted() {
-    this.fetchNotices()
+  created () {
+    this.init()
   },
   methods: {
+    init () {
+      getOneCourse({ courseId: this.$route.params.courseId })
+        .then(res => {
+          this.course = res
+          this.fetchNotices()
+        })
+        .catch(err => this.$setErrorMessage(err.response.data.msg))
+    },
     fetchNotices () {
       getNotices({ courseId: this.course.id }).then(res => {
         this.notices = res.data
       })
     },
-    handleCheckSlideList () {
-      this.$router.push({ name: 'teacher-course-slides' })
-    },
     handleChangeCode () {
       modifyCourse({ ...this.course, code: this.newCode }).then(res => {
         this.$setSuccessMessage('选课码修改成功')
@@ -104,6 +115,7 @@ export default {
 .c-input {
   margin-bottom: 24px;
 }
+
 .change-wrapper {
   display: flex;
   align-items: center;