Sfoglia il codice sorgente

feat: 与课程、课件相关的部分前后端逻辑

ChenYangfan 6 anni fa
parent
commit
bd67dda5c2

+ 4 - 3
src/App.vue

@@ -52,9 +52,10 @@ body {
   color: #aabaca;
   font-size: 20px;
   font-weight: bold;
-  display: flex;
-  align-items: center;
-  padding: 50px 0;
+  padding: 12px 0;
+  .no-content-warning-tip {
+    padding: 24px 0;
+  }
 }
 
 .tag {

+ 5 - 5
src/components/CourseCard.vue

@@ -1,14 +1,14 @@
 <template>
   <div class="course-card" title="点击修改课程详情" @click="handleClick">
-    <div class="course-card-title" :index="index ? index : '*'">课程名称</div>
-    <div class="course-card-bio">课程简介 - 史蒂夫和可史蒂夫史蒂夫,史蒂夫速度很快恢复健身房史蒂夫啊史蒂夫是否大。</div>
-    <div class="course-card-teacher">教师:史蒂夫</div>
+    <div class="course-card-title" :index="index ? index : '*'">{{course.name || '暂无课程名称'}}</div>
+    <div class="course-card-bio">{{course.bio || '暂无课程简介'}}</div>
+    <div class="course-card-teacher">{{course.teacherName}}</div>
   </div>
 </template>
 
 <script>
 export default {
-  props: ['index', 'clickHandler'],
+  props: ['index', 'clickHandler', 'course'],
   methods: {
     handleClick () {
       this.clickHandler(123)
@@ -22,7 +22,7 @@ export default {
 
 .course-card {
   font-weight: bold;
-  padding: 8px 8px 8px 16px;
+  padding: 12px 8px 12px 16px;
   border-left: 4px solid $input-background-color;
   transition: .2s;
   cursor: pointer;

+ 12 - 12
src/components/FileUploader.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="file-uploader">
     <label>
-      <span>{{iconMap.upload}} {{placeholder ? placeholder : '点击选取文件上传'}}</span>
+      <span>{{iconMap.upload}} {{fileName || placeholder || '点击选取文件上传'}}</span>
       <input
         type="file"
-        :value="value"
-        @input="$emit('input', $event.target.value)"
+        :accept="accept ? accept : ''"
+        @change="handleChange($event)"
       />
     </label>
   </div>
@@ -14,17 +14,17 @@
 <script>
 import iconMap from '@/utils/icon-map'
 export default {
-  model: {
-    prop: 'value',
-    event: 'input'
-  },
-  props: {
-    value: String,
-    placeholder: String
-  },
+  props: ['placeholder', 'accept'],
   data () {
     return {
-      iconMap: iconMap
+      iconMap: iconMap,
+      fileName: ''
+    }
+  },
+  methods: {
+    handleChange (e) {
+      this.fileName = e.target.files[0].name
+      this.$emit('change', e.target.files[0] || e.dataTransfer.files[0])
     }
   }
 }

+ 2 - 2
src/components/GlobalMessage.vue

@@ -55,7 +55,7 @@ export default {
   position: fixed;
   top: 20px;
   margin: 0 auto;
-  z-index: 2;
+  z-index: 5;
   animation: show 5s ease-out;
 }
 
@@ -72,7 +72,7 @@ export default {
 }
 
 .message {
-  color: #3a4a5a;
+  color: #5a6a7a;
   display: flex;
   align-items: center;
   justify-content: center;

+ 22 - 1
src/main.js

@@ -10,13 +10,34 @@ import { SET_MESSAGE } from '@/store/types/message-types'
 Vue.config.productionTip = false
 Vue.prototype.$uuid = uuid
 
-Vue.prototype.$showMessage = function ({ message, type = 'info' }) {
+Vue.prototype.$setMessage = function ({ message, type = 'info' }) {
   this.$store.commit(SET_MESSAGE, {
     message: message,
     type: type
   })
 }
 
+Vue.prototype.$setSuccessMessage = function (message) {
+  this.$store.commit(SET_MESSAGE, {
+    message: message,
+    type: 'success'
+  })
+}
+
+Vue.prototype.$setInfoMessage = function (message) {
+  this.$store.commit(SET_MESSAGE, {
+    message: message,
+    type: 'info'
+  })
+}
+
+Vue.prototype.$setErrorMessage = function (message) {
+  this.$store.commit(SET_MESSAGE, {
+    message: message,
+    type: 'error'
+  })
+}
+
 new Vue({
   router,
   store,

+ 0 - 2
src/server/code.js

@@ -10,7 +10,6 @@ export function sendEmailCode ({ email }) {
         email
       }
     })
-    .then(res => {})
 }
 
 export function sendPhoneCode ({ phone }) {
@@ -20,5 +19,4 @@ export function sendPhoneCode ({ phone }) {
         phone
       }
     })
-    .then(res => {})
 }

+ 8 - 3
src/views/api-test/ApiTest.vue

@@ -1,14 +1,14 @@
 <template>
   <div class="api-test">
     <h1 class="title">API 测试界面</h1>
-    <file-uploader class="mb-24" v-model="file"></file-uploader>
+    <file-uploader class="mb-24" @change="handleFileChange"></file-uploader>
     文件对象:{{ file }}
     <div>{{requestResult}}</div>
   </div>
 </template>
 
 <script>
-import { emailLogin, getUserInfo } from '@/server/user'
+import { getUserInfo, loginByEmail } from '@/server/user'
 import FileUploader from '@/components/FileUploader'
 
 export default {
@@ -20,10 +20,15 @@ export default {
     }
   },
   mounted() {
-    emailLogin({ email: 'liuqin@nju.edu.cn', password: '123456' }).then(res => {
+    loginByEmail({ email: 'liuqin@nju.edu.cn', password: '123456' }).then(res => {
       console.log(res)
       getUserInfo().then(res => console.log(res))
     })
+  },
+  methods: {
+    handleFileChange (file) {
+      this.file = file
+    }
   }
 }
 </script>

+ 11 - 10
src/views/login/Login.vue

@@ -17,7 +17,7 @@
 import CInput from '@/components/CInput'
 import CButton from '@/components/CButton'
 import Background from '@/components/Background'
-import { emailLogin } from '@/server/user'
+import { loginByEmail } from '@/server/user'
 
 export default {
   components: { CButton, CInput, Background },
@@ -33,19 +33,20 @@ export default {
     },
     handleSubmit () {
       if (!this.formChecked()) {
-        this.$showMessage({ message: '请填写完整的信息', type: 'info' })
+        this.$setInfoMessage('请填写完整的信息')
         return
       }
 
-      emailLogin({ email: this.email, password: this.password }).then(res => {
-        if (res) {
-          localStorage.setItem('userInfo', JSON.stringify(res))
-          if (res.type === 'TEACHER' || res.type === 'teacher') {
-            this.$router.push({ name: 'teacher' })
-          } else {
-            this.$router.push({ name: 'student' })
-          }
+      loginByEmail({ email: this.email, password: this.password }).then(res => {
+        this.$setSuccessMessage('欢迎回来,' + res.name)
+        localStorage.setItem('userInfo', JSON.stringify(res))
+        if (res.type === 'TEACHER' || res.type === 'teacher') {
+          this.$router.push({ name: 'teacher' })
+        } else {
+          this.$router.push({ name: 'student' })
         }
+      }).catch(err => {
+        this.$setErrorMessage(err.response.data.msg)
       })
     },
     handleToRegister () {

+ 18 - 7
src/views/register/Register.vue

@@ -121,7 +121,7 @@ export default {
     },
     handleSubmit () {
       if (!this.formChecked()) {
-        this.$showMessage({ message: this.message, type: 'error' })
+        this.$setErrorMessage(this.message)
       } else {
         register({
           name: this.name,
@@ -131,17 +131,24 @@ export default {
           phoneCode: this.phoneCode,
           password: this.password
         }).then(() => {
+          this.$setSuccessMessage('注册成功')
           this.$router.push({ name: 'login' })
+        }).catch(err => {
+          this.$setErrorMessage(err.response.data.msg)
         })
       }
     },
     handleSendEmailCode () {
-      this.$showMessage({ message: '正在尝试发送验证码', type: 'info' })
+      this.$setInfoMessage('正在尝试发送验证码')
       if (this.email.match(/.+@(nju.edu.cn|smail.nju.edu.cn)/)) {
         this.setEmailCodeCd()
-        sendEmailCode({ email: this.email })
+        sendEmailCode({ email: this.email }).then(res => {
+          this.$setSuccessMessage('邮箱验证码发送成功')
+        }).catch(err => {
+          this.$setErrorMessage(err.response.data.msg)
+        })
       } else {
-        this.$showMessage({ message: '本邮箱不符合南京大学校邮格式', type: 'error' })
+        this.$setErrorMessage('本邮箱不符合南京大学校邮格式')
       }
     },
     setEmailCodeCd () {
@@ -156,12 +163,16 @@ export default {
       }, 1000)
     },
     handleSendPhoneCode () {
-      this.$showMessage({ message: '正在尝试发送验证码', type: 'info' })
+      this.$setInfoMessage('正在尝试发送验证码')
       if (this.phone.match(/[0-9]{11}/)) {
         this.setPhoneCodeCd()
-        sendPhoneCode({ phone: this.phone })
+        sendPhoneCode({ phone: this.phone }).then(() => {
+          this.$setSuccessMessage('手机验证码已发送')
+        }).catch(err => {
+          this.$setErrorMessage(err.response.data.msg)
+        })
       } else {
-        this.$showMessage({ message: '手机号码应为大陆11位手机号码', type: 'error' })
+        this.$setErrorMessage('手机号码应为大陆11位手机号码')
       }
     },
     setPhoneCodeCd () {

+ 3 - 1
src/views/shared/UserHomeLayout.vue

@@ -14,7 +14,9 @@
         </template>
       </div>
       <div class="tab-container">
-        <router-view/>
+        <keep-alive>
+          <router-view/>
+        </keep-alive>
       </div>
     </div>
 

+ 4 - 6
src/views/teacher/components/CreateNewCourse.vue

@@ -47,16 +47,14 @@ export default {
     },
     handleCreate () {
       if (!this.formChecked()) {
-        this.$showMessage({
-          message: this.message
-        })
+        this.$setInfoMessage(this.message)
         return
       }
 
       createCourse({ name: this.name, bio: this.bio, code: this.code }).then(res => {
-        if (res) {
-          // todo
-        }
+        this.$setSuccessMessage('创建课程成功')
+      }).catch(err => {
+        this.$setErrorMessage(err.response.data.msg)
       })
     }
   }

+ 7 - 1
src/views/teacher/components/TeacherCourseSlides.vue

@@ -5,6 +5,8 @@
     <slide-card :click-handler="handleSlideCardClick"></slide-card>
     <slide-card :click-handler="handleSlideCardClick"></slide-card>
     <slide-card :click-handler="handleSlideCardClick"></slide-card>
+    <br>
+    <c-button class="full-width secondary" @click="handleToManager">管理本课程</c-button>
   </tab-side-component>
 </template>
 
@@ -12,9 +14,10 @@
 import TabSideComponent from '@/components/TabSideComponent'
 import BackTitle from '@/components/BackTitle'
 import SlideCard from '@/components/SlideCard'
+import CButton from '@/components/CButton'
 
 export default {
-  components: { SlideCard, BackTitle, TabSideComponent },
+  components: { CButton, SlideCard, BackTitle, TabSideComponent },
   data () {
     return {
       courseId: this.$route.params.courseId
@@ -23,6 +26,9 @@ export default {
   methods: {
     handleSlideCardClick (slideId) {
       this.$router.push({ name: 'teacher-explore-slide-detail', params: { slideId: slideId } })
+    },
+    handleToManager () {
+      this.$router.push({ name: 'teacher-course-detail', params: { courseId: this.courseId } })
     }
   }
 }

+ 38 - 17
src/views/teacher/tabs/CreateSlide.vue

@@ -6,20 +6,20 @@
       <label>
         <select v-model="selectedCourseId">
           <option disabled value="">[ 未填写 ]</option>
-          <option v-for="course of courses" :value="course.id" :key="course.id">
+          <option v-for="course in courses" :value="course.id" :key="course.id">
             {{ course.name }}
           </option>
         </select>
       </label>
     </div>
-    <div class="sub-title">幻灯片名称:</div>
+    <div class="sub-title">课件名称:</div>
     <c-input
-      v-model="userInput"
-      placeholder="请勿与同课程幻灯片重名"
+      v-model="slideName"
+      placeholder="请勿与同课程课件重名"
     />
-    <div class="sub-title">幻灯片文件 <span class="tag">目前支持 PDF 格式</span></div>
-    <file-uploader v-model="file"/>
-    <c-button class="my-button" @click.prevent="handleSubmit">
+    <div class="sub-title">课件文件 <span class="tag">目前支持 PDF 格式</span></div>
+    <file-uploader accept="application/pdf" class="mb-24" @change="handleFileChanged"/>
+    <c-button class="secondary full-width" :disabled="!formChecked()" @click="handleSubmit">
       添加
     </c-button>
   </div>
@@ -30,6 +30,7 @@ import CInput from '@/components/CInput'
 import CButton from '@/components/CButton'
 import FileUploader from '@/components/FileUploader'
 import { teacherGetCourses } from '@/server/course'
+import { createSlide } from '@/server/slide'
 
 export default {
   components: { FileUploader, CButton, CInput },
@@ -37,20 +38,44 @@ export default {
     return {
       courses: [],
       selectedCourseId: '',
-      userInput: '',
-      file: null
+      slideName: '',
+      file: null,
+      message: ''
     }
   },
   mounted () {
-    // todo
     teacherGetCourses({ sort: 'name' }).then(res => {
-      if (res) {
-        this.courses = res
-      }
+      this.courses = res.data
+    }).catch(err => {
+      this.$setErrorMessage('获取课件列表过程中:' + err.response.data.msg)
     })
   },
+  computed: {},
   methods: {
+    formChecked () {
+      if (!this.selectedCourseId) {
+        this.message = '请选择所属课程'
+        return false
+      }
+      if (!this.slideName) {
+        this.message = '请输入课件的名称'
+        return false
+      }
+      if (!this.file) {
+        this.message = '请上传课件文件'
+        return false
+      }
+      return true
+    },
     handleSubmit () {
+      if (!this.formChecked) {
+        this.$setInfoMessage(this.message)
+        return
+      }
+      createSlide({ courseId: this.selectedCourseId, name: this.slideName, file: this.file })
+    },
+    handleFileChanged (file) {
+      this.file = file || null
     }
   }
 }
@@ -88,8 +113,4 @@ select {
 .select-wrapper, .c-input {
   margin-bottom: 24px;
 }
-
-.my-button {
-  margin-top: 24px;
-}
 </style>

+ 27 - 4
src/views/teacher/tabs/TeacherCoursesManager.vue

@@ -2,9 +2,13 @@
   <div class="tab complex-tab">
     <div class="left">
       <h1 class="title">课程管理</h1>
-      <!--      <div class="no-content-warning">您还没有设置相关课程喔~</div>-->
-      <course-card :course="{}" :index="1" :clickHandler="handleClick"></course-card>
-      <course-card :course="{}" :index="2" :clickHandler="handleClick"></course-card>
+      <div class="sub-title">开设的课程</div>
+      <div class="no-content-warning" v-if="!courses || courses.length === 0">
+        <div class="no-content-warning-tip">你还没有过开设课程~</div>
+      </div>
+      <template v-for="(course, index) of courses">
+        <course-card :key="course.id" :course="course" :index="index + 1" :clickHandler="handleCourseCardClick"></course-card>
+      </template>
       <c-button @click="handleCreateNewCourse" class="full-width secondary">新建课程</c-button>
     </div>
     <router-view/>
@@ -14,14 +18,33 @@
 <script>
 import CButton from '@/components/CButton'
 import CourseCard from '@/components/CourseCard'
+import { teacherGetCourses } from '@/server/course'
 
 export default {
   components: { CButton, CourseCard },
+  data () {
+    return {
+      courses: []
+    }
+  },
+  mounted () {
+    this.updateInfo()
+  },
+  activated () {
+    this.updateInfo()
+  },
   methods: {
+    updateInfo () {
+      teacherGetCourses({ sort: 'name' }).then(res => {
+        this.courses = res.data
+      }).catch(err => {
+        this.$setErrorMessage('获取课程时:' + err.response.data.msg)
+      })
+    },
     handleCreateNewCourse () {
       this.$router.push('/teacher/courses/create-new')
     },
-    handleClick (courseId) {
+    handleCourseCardClick (courseId) {
       this.$router.push({ name: 'teacher-course-detail', params: { courseId: courseId } })
     }
   }

+ 35 - 5
src/views/teacher/tabs/TeacherExplore.vue

@@ -5,13 +5,23 @@
       <search-bar></search-bar>
       <section>
         <h1 class="sub-title">最近浏览</h1>
-        <slide-card :click-handler="handleSlideCardClick"></slide-card>
+        <div class="no-content-warning" v-if="!slides || slides.length === 0">
+          <div class="no-content-warning-tip">无最近浏览记录~</div>
+        </div>
+        <template v-for="slide of slides">
+          <slide-card :key="slide.id" :slide="slide" :click-handler="handleSlideCardClick"></slide-card>
+        </template>
         <br>
       </section>
       <section>
         <h1 class="sub-title">课程列表</h1>
-        <course-card :course="{}" :index="2" :clickHandler="handleCourseCardClick"></course-card>
-        <course-card :course="{}" :index="2" :clickHandler="handleCourseCardClick"></course-card>
+        <div class="no-content-warning" v-if="!courses || courses.length === 0">
+          <div class="no-content-warning-tip">你还没有过开设课程~</div>
+          <c-button class="full-width secondary" @click="handleToCreateCourse">去创建课程</c-button>
+        </div>
+        <template v-for="(course, index) of courses">
+          <course-card :key="course.id" :course="course" :index="index + 1" :clickHandler="handleCourseCardClick"></course-card>
+        </template>
       </section>
     </div>
     <router-view/>
@@ -23,13 +33,33 @@ import SearchBar from '@/components/SearchBar'
 import SlideCard from '@/components/SlideCard'
 import CourseCard from '@/components/CourseCard'
 import { teacherGetSlides } from '@/server/slide'
+import { teacherGetCourses } from '@/server/course'
+import CButton from '@/components/CButton'
 
 export default {
-  components: { CourseCard, SlideCard, SearchBar },
+  components: { CButton, CourseCard, SlideCard, SearchBar },
+  data () {
+    return {
+      courses: [],
+      slides: []
+    }
+  },
   mounted () {
-    teacherGetSlides({ size: 2, sort: 'updateAt,desc' })
+    teacherGetCourses({ sort: 'createAt' }).then(res => {
+      this.courses = res.data
+    }).catch(err => {
+      this.$setErrorMessage('获取课件时:' + err.response.data.msg)
+    })
+    teacherGetSlides({ size: 2, sort: 'updateAt,desc' }).then(res => {
+      this.slides = res.data
+    }).catch(err => {
+      this.$setErrorMessage(err.response.data.msg)
+    })
   },
   methods: {
+    handleToCreateCourse () {
+      this.$router.push({ name: 'create-new-course' })
+    },
     handleSlideCardClick (slideId) {
       this.$router.push({ name: 'teacher-explore-slide-detail', params: { slideId: slideId } })
     },