Просмотр исходного кода

Merge branch 'develop' of SEEC-II-FrontEnd-Team/SEEC-II-FrontEnd into master

WuXinyu 7 лет назад
Родитель
Сommit
fae23e8340
33 измененных файлов с 934 добавлено и 331 удалено
  1. 8 0
      mock/modules/assignment/index.js
  2. 23 1
      mock/modules/question/index.js
  3. 18 0
      mock/serializers/assignment/DocumentContentSerializer.js
  4. 1 1
      mock/serializers/user/UserSerializer.js
  5. 9 0
      src/api/assignment.js
  6. 1 0
      src/api/course.js
  7. 16 4
      src/api/question.js
  8. 17 8
      src/components/AssignmentCRUD/CreateAssignment/index.vue
  9. 12 2
      src/components/AssignmentCRUD/DeleteAssignment/index.vue
  10. 12 2
      src/components/AssignmentCRUD/UpdateAssignment/index.vue
  11. 1 1
      src/layouts/HomeLayout/HomeHeader/index.vue
  12. 2 1
      src/layouts/courseLayouts/LayoutStructure.vue
  13. 5 4
      src/views/login/Login.vue
  14. 5 4
      src/views/login/Register.vue
  15. 54 42
      src/views/student/Document/detail.vue
  16. 17 4
      src/views/student/Document/index.vue
  17. 165 5
      src/views/student/Home/index.vue
  18. 1 1
      src/views/student/Review/ReviewDocDetail/index.vue
  19. 24 8
      src/views/teacher/Code/detail.vue
  20. 18 4
      src/views/teacher/Code/index.vue
  21. 21 10
      src/views/teacher/Document/detail.vue
  22. 65 4
      src/views/teacher/Document/docContent.vue
  23. 66 55
      src/views/teacher/Document/index.vue
  24. 64 10
      src/views/teacher/Home/index.vue
  25. 100 45
      src/views/teacher/Question/index.vue
  26. 38 26
      src/views/teacher/Question/questionDetail.vue
  27. 36 25
      src/views/teacher/Result/index.vue
  28. 72 54
      src/views/teacher/Review/ReviewDetail/Detail/index.vue
  29. 19 5
      src/views/teacher/Review/ReviewDetail/GroupDocDetail/index.vue
  30. 8 0
      src/views/teacher/Review/ReviewDetail/Groups/index.vue
  31. 21 3
      src/views/teacher/Review/ReviewDetail/index.vue
  32. 14 2
      src/views/teacher/Review/index.vue
  33. 1 0
      vue.config.js

+ 8 - 0
mock/modules/assignment/index.js

@@ -7,6 +7,7 @@ const DocumentSerializer = require("../../serializers/assignment/DocumentSeriali
 const DocumentResultSerializer = require("../../serializers/assignment/DocumentResultSerializer");
 const codeProcedureSerializer = require("../../serializers/assignment/CodeProcedureSerializer");
 const groupSerializer = require("../../serializers/course/GroupSerializer");
+const DocumentContentSerializer = require("../../serializers/assignment/DocumentContentSerializer");
 
 //新建代码作业
 router.route("/course/:courseId/code").post((req, res) => {
@@ -178,4 +179,11 @@ router.route("/student/document/:documentId/result").get((req, res) => {
   });
 });
 
+router.route("/teacher/document/:documentId/content").get((req, res) => {
+  res.send({
+    code: 0,
+    data: DocumentContentSerializer()
+  });
+});
+
 module.exports = router;

+ 23 - 1
mock/modules/question/index.js

@@ -10,8 +10,30 @@ router.route("/teacher/questionType").get((req, res) => {
   });
 });
 
+//按题目分类获得题目(分页)
+router.route("/teacher/questions/type").get((req, res) => {
+  res.send({
+    data: [
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer(),
+      QuestionSerializer()
+    ],
+    pageNum: 1
+  });
+});
+
 //按题目分类获得题目(分页) || 根据作业分类获得题目
-router.route("/teacher/questions").get((req, res) => {
+router.route("/teacher/questions/assignmentType").get((req, res) => {
   res.send({
     data: [
       QuestionSerializer(),

+ 18 - 0
mock/serializers/assignment/DocumentContentSerializer.js

@@ -0,0 +1,18 @@
+const makeId = require("../../util/makeId");
+const groupSerializer = require("../course/GroupSerializer");
+const dayjs = require("dayjs");
+const makeLongMarkdown = require("../../util/makeLongMarkdown");
+
+/**
+ * @description 生成文档作业内容
+ * @returns {DocumentContentSerializerype}
+ */
+module.exports = () => {
+  return {
+    id: makeId(),
+    name: "购物车web项目",
+    group: groupSerializer(),
+    lastUpdateAt: dayjs("2018-10-21").unix(),
+    content: makeLongMarkdown()
+  };
+};

+ 1 - 1
mock/serializers/user/UserSerializer.js

@@ -14,7 +14,7 @@ const makeId = require("../../util/makeId");
  * @param role
  * @returns {UserSerializerType}
  */
-module.exports = (role = "STUDENT") => {
+module.exports = (role = "TEACHER") => {
   return {
     id: makeId(),
     role: role,

+ 9 - 0
src/api/assignment.js

@@ -258,3 +258,12 @@ export const getUnsubmittedDocGroupsByDocId = documentId => {
     `${ASSIGNMENT_MODULE}/teacher/document/${documentId}/unsubmitted/groups`
   );
 };
+
+/**
+ * 老师查看小组文档作业的内容
+ * @param documentId
+ * @return {Promise<data:DocumentContentSerializerType>}
+ */
+export const getDocContentByDocId = documentId => {
+  return request(`${ASSIGNMENT_MODULE}/teacher/document/${documentId}/content`);
+};

+ 1 - 0
src/api/course.js

@@ -25,6 +25,7 @@ export const getStudentCourses = () => {
  * @throws {StatusException} "课程名重复" | "课程选课码重复"
  */
 export const postTeacherCourse = ({ name, code }) => {
+  console.log(`name${name} code ${code}`);
   return request(`${COURSE_MODULE}/teacher`, {
     method: "POST",
     body: {

+ 16 - 4
src/api/question.js

@@ -13,10 +13,22 @@ export const getTeacherQuestionType = () => {
  * 按题目分类 || 作业分类获得题目
  * @returns {Promise<{data:QuestionSerializer[]}>}
  */
-export const getTeacherQuestions = () => {
-  return request(`${QUESTION_MODULE}/teacher/questions`);
+export const getQuestionsByAssignType = ({ type, page, limit }) => {
+  return request(
+    `${QUESTION_MODULE}/teacher/questions/assignmentType?type=${type ||
+      ""}&page=${page || 0}&limit=${limit || 20}`
+  );
+};
+/**
+ * 按题目分类 || 题目分类获得题目
+ * @returns {Promise<{data:QuestionSerializer[]}>}
+ */
+export const getQuestionByQuesType = ({ type, page, limit }) => {
+  return request(
+    `${QUESTION_MODULE}/teacher/questions/type?type=${type || ""}&page=${page ||
+      1}&limit=${limit || 10}`
+  );
 };
-
 /**
  * 根据关键字进行查找
  * @param {string} keyword
@@ -31,6 +43,6 @@ export const getTeacherKeywordQuestions = (keyword = "123") => {
  * @param {number | string} questionId
  * @returns {Promise<{data:QuestionSerializer}>}
  */
-export const getQuestionDetail = (questionId = 123) => {
+export const getQuestionDetail = questionId => {
   return request(`${QUESTION_MODULE}/question/${questionId}`);
 };

+ 17 - 8
src/components/AssignmentCRUD/CreateAssignment/index.vue

@@ -60,7 +60,7 @@
 
           <a
             class="button is-link"
-            :class="{ 'is-loading': submitting }"
+            :class="{ 'is-loading': loadingCreateAssignment }"
             @click="createAssignment()"
           >
             确认新建
@@ -88,7 +88,7 @@
             <p class="control">
               <button
                 class="button is-link"
-                :class="{ 'is-loading': submitting }"
+                :class="{ 'is-loading': loadingSearchQuestions }"
                 @click="search(searchContent)"
               >
                 查找
@@ -220,7 +220,9 @@ export default {
       },
       errorMassage: null,
       isServerError: false,
-      submitting: false
+      submitting: false,
+      loadingSearchQuestions: false,
+      loadingCreateAssignment: false
     };
   },
   methods: {
@@ -234,18 +236,20 @@ export default {
     /**
      * 搜索题目
      */
-    async search(content) {
+    search(content) {
       if (this.submitting) return;
       this.isServerError = false;
       this.$v.searchContent.$touch();
       if (!this.$v.searchContent.$invalid) {
+        this.loadingSearchQuestions = true;
         this.submitting = true;
         try {
-          await getTeacherKeywordQuestions(content).then(res => {
+          getTeacherKeywordQuestions(content).then(res => {
             this.questions = res.data;
+            this.isSearchModalActive = false;
+            this.loadingSearchQuestions = false;
+            this.isProblemListModalActive = true;
           });
-          this.isSearchModalActive = false;
-          this.isProblemListModalActive = true;
         } catch (e) {
           this.errorMassage = e.message;
           this.isServerError = true;
@@ -267,16 +271,18 @@ export default {
       if (this.submitting) return;
       this.$v.params.$touch();
       if (!this.$v.params.$invalid) {
+        this.loadingCreateAssignment = true;
         this.submitting = true;
-        this.isCreateModalActive = false;
         this.params["type"] = this.postParam;
         this.parseParams();
         if (this.assignType === 1) {
+          // 文档作业
           this.myPromise = postDocAssignment(
             this.params,
             parseInt(this.courseId)
           );
         } else {
+          // 代码作业
           this.myPromise = postCodeAssignment(
             this.params,
             parseInt(this.courseId)
@@ -287,6 +293,7 @@ export default {
             //创建成功
             this.$emit("sendCreatedAssignment", res.data);
             this.isCreateModalActive = false;
+            this.loadingCreateAssignment = false;
             if (this.assignType === 1) {
               this.$router.push({
                 path: `${this.prefix}/review?documentHwId=${res.data.id}`
@@ -294,6 +301,8 @@ export default {
             }
           } else {
             //创建失败
+            this.isCreateModalActive = false;
+            this.loadingCreateAssignment = false;
             this.$dialog.alert({
               title: "创建失败",
               message: res.message,

+ 12 - 2
src/components/AssignmentCRUD/DeleteAssignment/index.vue

@@ -1,5 +1,12 @@
 <template>
-  <div><a style="color:#b34141" @click="confirmDelete()">删除</a></div>
+  <div>
+    <a
+      style="color:#b34141"
+      :class="{ 'is-loading': loadingDelAssignment }"
+      @click="confirmDelete()"
+      >删除</a
+    >
+  </div>
 </template>
 
 <script>
@@ -18,12 +25,14 @@ export default {
   data() {
     return {
       myPromise: null,
-      submitting: false
+      submitting: false,
+      loadingDelAssignment: false
     };
   },
   methods: {
     confirmDelete() {
       if (this.submitting) return;
+      this.loadingDelAssignment = true;
       this.submitting = true;
       this.$dialog.confirm({
         message: "确认删除?",
@@ -47,6 +56,7 @@ export default {
                 hasIcon: true
               });
             }
+            this.loadingDelAssignment = false;
           });
         }
       });

+ 12 - 2
src/components/AssignmentCRUD/UpdateAssignment/index.vue

@@ -51,7 +51,13 @@
               :config="endAtConfig"
             ></flat-pickr>
           </b-field>
-          <a class="button is-link" @click="updateAssignment()"> 确认修改 </a>
+          <a
+            class="button is-link"
+            :class="{ 'is-loading': loadingUpdateAssignment }"
+            @click="updateAssignment()"
+          >
+            确认修改
+          </a>
         </div>
       </div>
     </b-modal>
@@ -101,7 +107,8 @@ export default {
         minDate: "today",
         maxDate: null
       },
-      submitting: false
+      submitting: false,
+      loadingUpdateAssignment: false
     };
   },
   methods: {
@@ -120,6 +127,7 @@ export default {
       if (this.submitting) return;
       this.$v.params.$touch();
       if (!this.$v.params.$invalid) {
+        this.loadingUpdateAssignment = true;
         this.submitting = true;
         this.isUpdateModalActive = false;
         this.parseParams();
@@ -131,7 +139,9 @@ export default {
         this.myPromise.then(res => {
           if (res.code === 0) {
             this.$emit("updateDocData", res.data);
+            this.loadingUpdateAssignment = false;
           } else {
+            this.loadingUpdateAssignment = false;
             this.$dialog.alert({
               title: "修改失败",
               message: res.message,

+ 1 - 1
src/layouts/HomeLayout/HomeHeader/index.vue

@@ -2,7 +2,7 @@
   <default-header>
     <div class="container">
       <div class="navbar-brand">
-        <a class="navbar-item header-logo"> SEEC </a>
+        <router-link to="/" class="navbar-item header-logo"> SEEC </router-link>
         <a class="navbar-item"><b-icon pack="fab" icon="github"></b-icon></a>
         <a class="navbar-item"><b-icon pack="fab" icon="twitter"></b-icon></a>
       </div>

+ 2 - 1
src/layouts/courseLayouts/LayoutStructure.vue

@@ -3,7 +3,7 @@
     <div class="course-layout">
       <div class="menu-section">
         <div class="menu-sticky">
-          <div class="header-logo">SEEC</div>
+          <router-link tag="div" to="/" class="header-logo">SEEC</router-link>
           <div class="course-detail">
             {{ course.name }}
             <b-loading
@@ -127,6 +127,7 @@ $top-section-height: 4.25rem;
       border-bottom: 2px solid rgba(255, 255, 255, 0.16);
       /*background-image: linear-gradient(to left, #4f62e9 0%, #161aa7 100%);*/
       padding-left: 24px;
+      cursor: pointer;
       a {
         color: #3d70b2;
       }

+ 5 - 4
src/views/login/Login.vue

@@ -33,7 +33,7 @@
     <div style="padding-top: 40px;">
       <button
         :class="{ 'is-loading': submitting }"
-        class="login"
+        class="button login"
         @click="loginAction"
       >
         登 陆
@@ -43,7 +43,7 @@
       <router-link
         :to="{ name: 'REGISTER' }"
         :class="{ 'is-text': true }"
-        class="register"
+        class="button register"
         type="submit"
         >没有账户,注册
       </router-link>
@@ -125,16 +125,17 @@ export default {
 .register {
   color: #7396fd;
   margin-top: 20px;
-  padding: 10px 0px 10px 0px;
+  /*padding: 10px 0px 10px 0px;*/
   font-size: 1rem;
   width: 100%;
   display: flex;
   justify-content: center;
   border: 1px solid #82b1ed;
+  text-decoration: none;
 }
 .login {
   color: #ffffff;
-  padding: 10px 0px 10px 0px;
+  /*padding: 10px 0px 10px 0px;*/
   display: flex;
   justify-content: center;
   font-size: 1rem;

+ 5 - 4
src/views/login/Register.vue

@@ -70,7 +70,7 @@
     <div style="padding-top: 20px;">
       <button
         :class="{ 'is-loading': submitting }"
-        class="register"
+        class="button register"
         @click="registerAction"
       >
         注 册
@@ -80,7 +80,7 @@
     <router-link
       :to="{ name: 'LOGIN' }"
       :class="{ 'is-text': true }"
-      class="login"
+      class="button login"
       type="submit"
       >已有账户,登陆
     </router-link>
@@ -175,7 +175,7 @@ export default {
 }
 .register {
   color: #ffffff;
-  padding: 10px 0 10px 0;
+  /*padding: 10px 0 10px 0;*/
   display: flex;
   justify-content: center;
   font-size: 1rem;
@@ -187,11 +187,12 @@ export default {
 .login {
   color: #7396fd;
   margin-top: 20px;
-  padding: 10px 0 10px 0;
+  /*padding: 10px 0 10px 0;*/
   font-size: 1rem;
   width: 100%;
   display: flex;
   justify-content: center;
   border: 1px solid #82b1ed;
+  text-decoration: none;
 }
 </style>

+ 54 - 42
src/views/student/Document/detail.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <page-header
+      :loading="loadingDocDetail"
       :routes="[
         { name: '文档作业', path: 'document' },
         { name: `作业 `, path: $route.path }
@@ -12,53 +13,57 @@
       <template slot="content">
         {{ assignmentInfo.description }}
       </template>
-      <template slot="action" v-if="resultDisplay">
-        <a class="button is-primary" v-if="resultDisplay"
-          >成绩:{{ docResult.result }}</a
-        >
+      <template slot="action" v-if="loadingDocResult">
+        <a class="button is-primary">成绩:{{ docResult.result }}</a>
       </template>
     </page-header>
     <page-body>
-      <div class="page-section">
-        <!--第一行-->
-        <div class="columns is-desktop">
-          <div class="column">
-            <h5 class="title is-5">题目名称</h5>
-            <div class="subtitle is-6">
-              {{ detail.problem ? detail.problem.name : "" }}
+      <div>
+        <page-section-loading :show="loadingDocDetail" />
+        <div v-if="!loadingDocDetail" class="page-section">
+          <!--第一行-->
+          <div class="columns is-desktop">
+            <div class="column">
+              <h5 class="title is-5">题目名称</h5>
+              <div class="subtitle is-6" v-if="isAvailable">
+                {{ detail.problem ? detail.problem.name : "" }}
+              </div>
+              <div class="subtitle is-6" v-else>题目开始进行才可查看</div>
             </div>
-          </div>
-          <div class="column">
-            <h5 class="title is-5">状态:</h5>
-            <div class="subtitle is-6">
-              <document-state-tag :state="detail.status"></document-state-tag>
+            <div class="column">
+              <h5 class="title is-5">状态:</h5>
+              <div class="subtitle is-6">
+                <document-state-tag :state="detail.status"></document-state-tag>
+              </div>
             </div>
           </div>
-        </div>
-        <!--第二行-->
-        <div class="columns is-desktop">
-          <div class="column">
-            <h5 class="title is-5">开始时间</h5>
-            <div class="subtitle is-6">
-              {{ displayUnixTime(detail.startAt) }}
+          <!--第二行-->
+          <div class="columns is-desktop">
+            <div class="column">
+              <h5 class="title is-5">开始时间</h5>
+              <div class="subtitle is-6">
+                {{ displayUnixTime(detail.startAt) }}
+              </div>
+            </div>
+            <div class="column">
+              <h5 class="title is-5">结束时间</h5>
+              <div class="subtitle is-6">
+                {{ displayUnixTime(detail.endAt) }}
+              </div>
             </div>
           </div>
-          <div class="column">
-            <h5 class="title is-5">结束时间</h5>
-            <div class="subtitle is-6">{{ displayUnixTime(detail.endAt) }}</div>
-          </div>
-        </div>
-        <div class="columns is-desktop">
-          <div class="column">
-            <h5 class="title is-5">Git地址</h5>
-            <div class="subtitle is-6">{{ docResult.url }}</div>
+          <div class="columns is-desktop" v-show="isAvailable">
+            <div class="column">
+              <h5 class="title is-5">Git地址</h5>
+              <div class="subtitle is-6">{{ docResult.url }}</div>
+            </div>
           </div>
-        </div>
-        <div class="columns is-desktop">
-          <div class="column">
-            <h5 class="title is-5">题目描述</h5>
-            <div class="subtitle is-6">
-              {{ detail.problem ? detail.problem.description : "" }}
+          <div class="columns is-desktop" v-show="isAvailable">
+            <div class="column">
+              <h5 class="title is-5">题目描述</h5>
+              <div class="subtitle is-6">
+                {{ detail.problem ? detail.problem.description : "" }}
+              </div>
             </div>
           </div>
         </div>
@@ -75,6 +80,7 @@ import { getDocAssignmentDetailByDocuId } from "@/api/assignment";
 import { getDocResultById } from "@/api/assignment";
 import DocumentStateTag from "@/components/DocumentStateTag";
 import timeMixins from "@/mixins/time";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 const assignmentInfo = {
   id: 0,
@@ -86,22 +92,26 @@ const assignmentInfo = {
 
 export default {
   components: {
+    PageSectionLoading,
     PageBody,
     PageHeader,
     DocumentStateTag
   },
   mixins: [timeMixins],
   mounted() {
+    this.loadingDocDetail = true;
     this.assignmentInfo = this.$route.query.assignmentInfo;
     getDocAssignmentDetailByDocuId(this.assignmentInfo.id).then(res => {
-      console.log(res.data);
       this.detail = res.data;
+      this.loadingDocDetail = false;
+      this.isAvailable =
+        this.detail.status === "AVAILABLE" || this.detail.status === "FINISHED";
     });
     getDocResultById(this.assignmentInfo.id).then(res => {
       this.docResult = res.data;
-      console.log("doc.result" + this.docResult.result);
+      // 有成绩
       if (parseInt(this.docResult.result) !== -1) {
-        this.resultDisplay = true;
+        this.loadingDocResult = true;
       }
     });
   },
@@ -110,7 +120,9 @@ export default {
       assignmentInfo,
       detail: {},
       docResult: {},
-      resultDisplay: false
+      loadingDocResult: false,
+      loadingDocDetail: false,
+      isAvailable: false
     };
   },
   computed: {

+ 17 - 4
src/views/student/Document/index.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
-    <page-header :routes="[{ name: '文档作业', path: 'document' }]">
+    <page-header
+      :loading="loadingDocList"
+      :routes="[{ name: '文档作业', path: 'document' }]"
+    >
       <template slot="title">
         文档作业列表
       </template>
@@ -10,7 +13,15 @@
     </page-header>
     <page-body>
       <div class="columns is-multiline is-desktop">
-        <div :key="item.id" v-for="item in docList" class="column is-4">
+        <div v-if="loadingDocList" class="column is-4-desktop">
+          <card loading />
+        </div>
+        <div
+          v-else
+          :key="item.id"
+          v-for="item in docList"
+          class="column is-4-desktop"
+        >
           <router-link
             :to="{
               path: `${$route.path}/${item.id}`,
@@ -47,9 +58,10 @@ export default {
   },
   mixins: [timeMixins],
   mounted() {
-    console.log(this.$route.path);
+    this.loadingDocList = true;
     getDocAssignmentListByCrs(this.course.id).then(res => {
       this.docList = res.data;
+      this.loadingDocList = false;
     });
   },
   computed: {
@@ -65,7 +77,8 @@ export default {
         description: "",
         startAt: new Date(),
         endAt: new Date()
-      }
+      },
+      loadingDocList: false
     };
   },
   methods: {}

+ 165 - 5
src/views/student/Home/index.vue

@@ -2,38 +2,198 @@
   <div>
     <section class="hero">
       <div class="hero-body">
-        <div class="container">
+        <div class="container hint">
           <h1 class="title">我的课程</h1>
           <h2 class="subtitle">展示我加入的全部课程</h2>
+          <img
+            class="newSubject"
+            src="../../../assets/white.png"
+            @click="joinCouseActive = true"
+          />
         </div>
       </div>
     </section>
     <div class="container">
       <div class="columns is-multiline is-desktop">
-        <div :key="item.id" v-for="item in courses" class="column is-4-desktop">
+        <div v-if="isStudentCourseLoading" class="column is-4-desktop">
+          <card loading />
+        </div>
+        <div
+          v-else
+          :key="item.id"
+          v-for="item in courses"
+          class="column is-4-desktop"
+        >
           <router-link :to="`/course-student/${item.id}/dashboard`">
             <card hoverable>{{ item.name }}</card>
           </router-link>
         </div>
       </div>
     </div>
+    <!-- 新建课程模态框 -->
+    <b-modal :active.sync="joinCouseActive" :width="320" scroll="keep">
+      <div class="card">
+        <div class="title">加入课程</div>
+        <div class="content">
+          <b-notification
+            class="notification-self"
+            type="is-danger"
+            :active.sync="isServerError"
+          >
+            {{ errorMassage }}
+          </b-notification>
+          <b-field
+            label="选课码"
+            :type="$v.coursenum.$error ? 'is-danger' : ''"
+            :message="
+              $v.coursenum.$error
+                ? [
+                    !$v.coursenum.minLength ? '选课码不少于4个字符' : undefined,
+                    !$v.coursenum.maxLength
+                      ? '选课码不多于10个字符'
+                      : undefined,
+                    !$v.coursenum.required ? '请填写选课码' : undefined,
+                    !$v.coursenum.alphaNum ? '选课码应为字母或数字' : undefined
+                  ]
+                : ''
+            "
+          >
+            <b-input
+              v-model="$v.coursenum.$model"
+              placeholder="请输入选课码"
+            ></b-input>
+          </b-field>
+          <a
+            :class="['button', 'is-link', { 'is-loading': submitting }]"
+            @click="joinSubject"
+            >加入课程</a
+          >
+        </div>
+      </div>
+    </b-modal>
   </div>
 </template>
 
 <script>
 import Card from "@/components/Card";
-import { getStudentCourses } from "@/api/course";
+import { getStudentCourses, postStudentCourse } from "@/api/course";
+import {
+  required,
+  minLength,
+  maxLength,
+  alphaNum
+} from "vuelidate/lib/validators";
+
 export default {
   components: {
     Card
   },
+  validations: {
+    coursenum: {
+      required,
+      minLength: minLength(4),
+      maxLength: maxLength(10),
+      alphaNum
+    }
+  },
   data() {
     return {
-      courses: []
+      courses: [],
+      joinCouseActive: false,
+      coursenum: "",
+      submitting: false,
+      isServerError: false,
+      errorMassage: "服务器错误,请稍后重试",
+      isStudentCourseLoading: true
     };
   },
   mounted() {
-    getStudentCourses().then(value => (this.courses = value.data));
+    this.getCourses();
+  },
+  methods: {
+    getCourses() {
+      getStudentCourses().then(value => {
+        this.courses = value.data;
+        this.isStudentCourseLoading = false;
+      });
+    },
+    //加入课程
+    joinSubject() {
+      if (this.submitting) return false;
+      this.$v.$touch();
+      if (!this.$v.$invalid) {
+        this.submitting = true;
+        this.isServerError = false;
+        postStudentCourse(this.coursenum)
+          .then(res => {
+            this.submitting = false;
+            this.joinCouseActive = false;
+            this.$toast.open({
+              message: `${res.data.name} 选课成功`,
+              type: "is-success"
+            });
+            this.getCourses();
+          })
+          .catch(e => {
+            this.submitting = false;
+            this.isServerError = true;
+            this.errorMassage = e.message;
+          });
+      }
+    }
   }
 };
 </script>
+<style lang="scss" scoped>
+@import "../../../assets/scss/app";
+.container.hint {
+  padding: 30px 20px 20px 30px;
+  /*background-image: linear-gradient(to right, #6a11cb 0%, #2575fc 100%);*/
+  /*TODO: 寻找质量更高的图片以防止对比度过低 */
+  background-image: url("../../../assets/login_background.png");
+  /*background-size: cover;*/
+  box-shadow: $default-shadow;
+  border-radius: $default-radius;
+  .title {
+    color: #ffffff;
+  }
+  .subtitle {
+    margin-top: -0.8em;
+    font-size: 1rem;
+    font-weight: bold;
+    color: #ffffff;
+  }
+}
+.card {
+  padding: 30px;
+  .title {
+    font-size: 1.25rem;
+    small {
+      color: gray;
+      font-size: 1rem;
+    }
+  }
+  .content {
+    a {
+      margin-top: 30px;
+      width: 100%;
+    }
+  }
+}
+.newSubject {
+  height: 80px;
+  width: 80px;
+  position: absolute;
+  bottom: -40px;
+  left: 20px;
+  transition-property: all;
+  transition-duration: 0.2s;
+
+  &:hover {
+    height: 90px;
+    width: 90px;
+    bottom: -45px;
+    left: 15px;
+  }
+}
+</style>

+ 1 - 1
src/views/student/Review/ReviewDocDetail/index.vue

@@ -81,7 +81,7 @@ export default {
         checkList: []
       },
       submitLoading: false,
-      fetchLoading: false
+      loadingDocContent: false
     };
   },
   mixins: [timeMixins],

+ 24 - 8
src/views/teacher/Code/detail.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <page-header
+      :loading="loadingCommittedGroups"
       :routes="[
         { name: '代码作业', path: 'code' },
         { name: '作业详情', path: 'code/${assignmentInfo.id}' }
@@ -16,9 +17,15 @@
 
     <page-body>
       <div class="data-table">
-        <b-tabs type="is-boxed">
-          <b-tab-item label="已提交">
-            <b-table :data="codeProcedureList" detailed detail-key="id">
+        <b-tabs>
+          <b-tab-item :label="submitInfo.yes">
+            <page-section-loading :show="loadingCommittedGroups" />
+            <b-table
+              v-if="!loadingCommittedGroups"
+              :data="codeProcedureList"
+              detailed
+              detail-key="id"
+            >
               <template slot-scope="props">
                 <b-table-column
                   field="id"
@@ -94,7 +101,8 @@
           </b-tab-item>
 
           <b-tab-item :label="submitInfo.no">
-            <b-table :data="unsubmitedGroups">
+            <page-section-loading :show="loadingUncommittedGroups" />
+            <b-table v-if="!loadingUncommittedGroups" :data="unsubmitedGroups">
               <template slot-scope="props">
                 <b-table-column
                   field="id"
@@ -128,6 +136,7 @@ import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import { getCodeProcedureListByCodeId } from "@/api/assignment";
 import { getUnsubmittedCodeGroupsList } from "@/api/assignment";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 const assignmentInfo = {
   id: 0,
@@ -152,17 +161,22 @@ const status = {
 export default {
   components: {
     PageBody,
-    PageHeader
+    PageHeader,
+    PageSectionLoading
   },
   mounted() {
+    this.loadingUncommittedGroups = true;
+    this.loadingCommittedGroups = true;
     this.assignmentInfo = this.$route.query.assignmentInfo;
     getCodeProcedureListByCodeId(123).then(res => {
       this.codeProcedureList = res.data;
       this.submitInfo.yes = "已提交:" + this.codeProcedureList.length;
+      this.loadingCommittedGroups = false;
     });
     getUnsubmittedCodeGroupsList(123).then(res => {
       this.unsubmitedGroups = res.data;
       this.submitInfo.no = "未提交:" + this.unsubmitedGroups.length;
+      this.loadingUncommittedGroups = false;
     });
   },
   data() {
@@ -174,9 +188,11 @@ export default {
       tag,
       status,
       submitInfo: {
-        yes: "",
-        no: ""
-      }
+        yes: "已提交",
+        no: "未提交"
+      },
+      loadingCommittedGroups: false,
+      loadingUncommittedGroups: false
     };
   },
   methods: {

+ 18 - 4
src/views/teacher/Code/index.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
-    <page-header :routes="[{ name: '代码作业', path: 'code' }]">
+    <page-header
+      :loading="loadingCodeList"
+      :routes="[{ name: '代码作业', path: 'code' }]"
+    >
       <template slot="title">
         代码作业列表
       </template>
@@ -18,7 +21,13 @@
 
     <page-body>
       <div class="data-table">
-        <b-table :data="codeData" detailed detail-key="id">
+        <page-section-loading :show="loadingCodeList" />
+        <b-table
+          v-if="!loadingCodeList"
+          :data="codeData"
+          detailed
+          detail-key="id"
+        >
           <template slot-scope="props">
             <b-table-column field="id" label="ID" numeric sortable centered>
               {{ props.row.id }}
@@ -90,6 +99,7 @@ import { mapState } from "vuex";
 import DeleteAssignment from "@/components/AssignmentCRUD/DeleteAssignment";
 import UpdateAssignment from "@/components/AssignmentCRUD/UpdateAssignment";
 import CreateAssignment from "@/components/AssignmentCRUD/CreateAssignment";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 export default {
   components: {
@@ -97,18 +107,22 @@ export default {
     PageHeader,
     DeleteAssignment,
     UpdateAssignment,
-    CreateAssignment
+    CreateAssignment,
+    PageSectionLoading
   },
   mixins: [timeMixins],
   mounted() {
+    this.loadingCodeList = true;
     getCodeAssignmentListByTeacher(this.course.id).then(res => {
       this.codeData = res.data;
+      this.loadingCodeList = false;
     });
   },
   data() {
     return {
       codeData: [],
-      assignType: 0
+      assignType: 0,
+      loadingCodeList: false
     };
   },
   methods: {

+ 21 - 10
src/views/teacher/Document/detail.vue

@@ -1,7 +1,7 @@
 <template>
   <div>
     <page-header
-      :loading="isLoading"
+      :loading="loadingCommittedGroups"
       :routes="[
         { name: '文档作业', path: 'document' },
         { name: `作业详情`, path: `document` }
@@ -22,7 +22,13 @@
       <div class="data-table">
         <b-tabs>
           <b-tab-item :label="submitInfo.yes">
-            <b-table :data="submittedData" detailed detail-key="id">
+            <page-section-loading :show="loadingCommittedGroups" />
+            <b-table
+              v-if="!loadingCommittedGroups"
+              :data="submittedData"
+              detailed
+              detail-key="id"
+            >
               <template slot-scope="props">
                 <b-table-column
                   field="id"
@@ -82,7 +88,8 @@
           </b-tab-item>
 
           <b-tab-item :label="submitInfo.no">
-            <b-table :data="unsubmitedGroups">
+            <page-section-loading :show="loadingUncommittedGroups" />
+            <b-table v-if="!loadingUncommittedGroups" :data="unsubmitedGroups">
               <template slot-scope="props">
                 <b-table-column
                   field="id"
@@ -117,6 +124,7 @@ import PageBody from "@/components/PageBody/index";
 import { getSubmittedDocResultListByDocId } from "@/api/assignment";
 import { getUnsubmittedDocGroupsByDocId } from "@/api/assignment";
 import { mapState } from "vuex";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 export default {
   computed: {
@@ -126,24 +134,26 @@ export default {
   },
   components: {
     PageBody,
-    PageHeader
+    PageHeader,
+    PageSectionLoading
   },
   created() {
     this.docId = this.$route.query.docId;
     this.question = this.$route.query.question;
   },
   mounted() {
-    this.isLoading = true;
+    this.loadingCommittedGroups = true;
+    this.loadingUncommittedGroups = true;
     getSubmittedDocResultListByDocId(this.docId).then(res => {
       this.status = res.data.status;
       this.submittedData = res.data.result;
       this.submitInfo.yes = "已提交:" + this.submittedData.length;
-      this.isLoading = false;
+      this.loadingCommittedGroups = false;
     });
     getUnsubmittedDocGroupsByDocId(this.docId).then(res => {
       this.unsubmitedGroups = res.data;
       this.submitInfo.no = "未提交:" + this.unsubmitedGroups.length;
-      this.isLoading = false;
+      this.loadingUncommittedGroups = false;
     });
   },
   data() {
@@ -154,10 +164,11 @@ export default {
       question: null,
       docId: null,
       submitInfo: {
-        yes: "",
-        no: ""
+        yes: "已提交",
+        no: "未提交"
       },
-      isLoading: false
+      loadingCommittedGroups: false,
+      loadingUncommittedGroups: false
     };
   }
 };

+ 65 - 4
src/views/teacher/Document/docContent.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <page-header
+      :loading="loadingDocContent"
       :routes="[
         { name: '文档作业', path: 'document' },
         {
@@ -13,27 +14,87 @@
       <template slot="title">
         文档内容
       </template>
+      <template slot="content">
+        题目名称:{{ contentResult.name }} <br />
+        <br />
+        小组成员:<span
+          v-for="member in contentResult.group.members"
+          :key="member.id"
+          >{{ member.username }}&emsp;</span
+        >
+      </template>
+      <template slot="extraContent">
+        上次修改时间:{{ displayUnixTime(contentResult.lastUpdateAt) }}
+      </template>
     </page-header>
-    <page-body> <div class="data-table">文档内容</div> </page-body>
+    <page-body>
+      <div class="data-table">
+        <page-section-loading :show="loadingDocContent" />
+        <div v-if="!loadingDocContent" class="page-section">
+          <div v-if="contentResult.content" class="content">
+            <vue-markdown>{{ contentResult.content }}</vue-markdown>
+          </div>
+        </div>
+      </div>
+    </page-body>
   </div>
 </template>
 
 <script>
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
+import { getDocContentByDocId } from "@/api/assignment";
+import VueMarkdown from "vue-markdown";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
+import timeMixins from "@/mixins/time";
 export default {
+  mixins: [timeMixins],
   components: {
     PageBody,
-    PageHeader
+    PageHeader,
+    VueMarkdown,
+    PageSectionLoading
   },
   data() {
     return {
-      docId: null
+      docId: null,
+      contentResult: {
+        content: "",
+        group: "",
+        lastUpdateAt: "",
+        name: ""
+      },
+      loadingDocContent: false
     };
   },
   created() {
     this.docId = this.$route.query.assignId;
   },
-  mounted() {}
+  mounted() {
+    this.loadingDocContent = true;
+    getDocContentByDocId(this.docId).then(res => {
+      this.contentResult = res.data;
+      this.loadingDocContent = false;
+    });
+  }
 };
 </script>
+<style lang="scss" scoped>
+@import "../../../assets/scss/app";
+.right-part {
+  margin-bottom: -1.5rem;
+  margin-right: -1.5rem;
+  background: white;
+  border-top: 1px solid #eff2f7;
+  position: sticky;
+  top: 0;
+  max-height: 100vh;
+  overflow-y: auto;
+  box-shadow: $default-shadow;
+}
+
+.action-button {
+  width: 100%;
+  border-radius: 0;
+}
+</style>

+ 66 - 55
src/views/teacher/Document/index.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
-    <page-header :routes="[{ name: '文档作业', path: 'document' }]">
+    <page-header
+      :loading="loadingDocList"
+      :routes="[{ name: '文档作业', path: 'document' }]"
+    >
       <template slot="title">
         文档作业列表
       </template>
@@ -17,63 +20,66 @@
     </page-header>
 
     <page-body>
-      <div class="data-table">
-        <b-table :data="docData" detailed detail-key="id">
-          <template slot-scope="props">
-            <b-table-column field="id" label="ID" numeric sortable centered>
-              {{ props.row.id }}
-            </b-table-column>
+      <div>
+        <page-section-loading :show="loadingDocList" />
+        <div v-if="!loadingDocList" class="data-table">
+          <b-table :data="docData" detailed detail-key="id">
+            <template slot-scope="props">
+              <b-table-column field="id" label="ID" numeric sortable centered>
+                {{ props.row.id }}
+              </b-table-column>
 
-            <b-table-column label="作业名称" centered>
-              <router-link
-                :to="{
-                  path: `${$route.path}/${props.row.id}`,
-                  query: { docId: props.row.id, question: props.row.problem }
-                }"
-              >
-                {{ props.row.name }}
-              </router-link>
-            </b-table-column>
-            <b-table-column label="状态" centered>
-              {{ props.row.status }}
-            </b-table-column>
+              <b-table-column label="作业名称" centered>
+                <router-link
+                  :to="{
+                    path: `${$route.path}/${props.row.id}`,
+                    query: { docId: props.row.id, question: props.row.problem }
+                  }"
+                >
+                  {{ props.row.name }}
+                </router-link>
+              </b-table-column>
+              <b-table-column label="状态" centered>
+                {{ props.row.status }}
+              </b-table-column>
 
-            <b-table-column label="开始时间" centered>
-              {{ displayUnixTime(props.row.startAt) }}
-            </b-table-column>
+              <b-table-column label="开始时间" centered>
+                {{ displayUnixTime(props.row.startAt) }}
+              </b-table-column>
 
-            <b-table-column label="结束时间" centered>
-              {{ displayUnixTime(props.row.endAt) }}
-            </b-table-column>
-            <b-table-column label="修改" centered>
-              <update-assignment
-                :assign-type="assignType"
-                :update-item="props.row"
-                @updateDocData="updateData($event)"
-              ></update-assignment>
-            </b-table-column>
-            <b-table-column label="删除" centered>
-              <delete-assignment
-                :del-id="props.row.id"
-                :assign-type="assignType"
-                @del="deleteAssignment(props.row.id)"
-              ></delete-assignment>
-            </b-table-column>
-          </template>
+              <b-table-column label="结束时间" centered>
+                {{ displayUnixTime(props.row.endAt) }}
+              </b-table-column>
+              <b-table-column label="修改" centered>
+                <update-assignment
+                  :assign-type="assignType"
+                  :update-item="props.row"
+                  @updateDocData="updateData($event)"
+                ></update-assignment>
+              </b-table-column>
+              <b-table-column label="删除" centered>
+                <delete-assignment
+                  :del-id="props.row.id"
+                  :assign-type="assignType"
+                  @del="deleteAssignment(props.row.id)"
+                ></delete-assignment>
+              </b-table-column>
+            </template>
 
-          <template slot="detail" slot-scope="props">
-            <article class="media">
-              <div class="media-content">
-                <div class="content">
-                  <p>
-                    <strong>作业描述:</strong>
-                    <small>{{ props.row.description }}</small> <br />
-                  </p>
+            <template slot="detail" slot-scope="props">
+              <article class="media">
+                <div class="media-content">
+                  <div class="content">
+                    <p>
+                      <strong>作业描述:</strong>
+                      <small>{{ props.row.description }}</small> <br />
+                    </p>
+                  </div>
                 </div>
-              </div>
-            </article>
-          </template>
-        </b-table>
+              </article>
+            </template>
+          </b-table>
+        </div>
       </div>
     </page-body>
   </div>
@@ -88,6 +94,7 @@ import { mapState } from "vuex";
 import DeleteAssignment from "@/components/AssignmentCRUD/DeleteAssignment";
 import UpdateAssignment from "@/components/AssignmentCRUD/UpdateAssignment";
 import CreateAssignment from "@/components/AssignmentCRUD/CreateAssignment";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 export default {
   computed: {
@@ -100,18 +107,22 @@ export default {
     PageHeader,
     DeleteAssignment,
     UpdateAssignment,
-    CreateAssignment
+    CreateAssignment,
+    PageSectionLoading
   },
   mixins: [timeMixins],
   mounted() {
+    this.loadingDocList = true;
     getDocAssignmentListTeacherByCrs(this.course.id).then(res => {
       this.docData = res.data;
+      this.loadingDocList = false;
     });
   },
   data() {
     return {
       assignType: 1,
-      docData: []
+      docData: [],
+      loadingDocList: false
     };
   },
   methods: {

+ 64 - 10
src/views/teacher/Home/index.vue

@@ -8,25 +8,35 @@
           <img
             class="newSubject"
             src="../../../assets/white.png"
-            @click="isCardModalActive = true"
+            @click="newSubjectActive = true"
           />
         </div>
       </div>
     </section>
     <div class="container">
       <div class="columns is-multiline is-desktop">
-        <div :key="item.id" v-for="item in courses" class="column is-4">
+        <div v-if="isTeacherCourseLoading" class="column is-4-desktop">
+          <card loading />
+        </div>
+        <div v-else :key="item.id" v-for="item in courses" class="column is-4">
           <router-link :to="`/course-teacher/${item.id}/dashboard`">
             <card hoverable>{{ item.name }}</card>
           </router-link>
         </div>
       </div>
     </div>
-    <!-- 成绩比例调控模态框 -->
-    <b-modal :active.sync="isCardModalActive" :width="320" scroll="keep">
+    <!-- 新建课程模态框 -->
+    <b-modal :active.sync="newSubjectActive" :width="320" scroll="keep">
       <div class="card">
         <div class="title">新建课程</div>
         <div class="content">
+          <b-notification
+            class="notification-self"
+            type="is-danger"
+            :active.sync="isServerError"
+          >
+            {{ errorMassage }}
+          </b-notification>
           <b-field
             label="课程名"
             :type="$v.coursename.$error ? 'is-danger' : ''"
@@ -70,7 +80,11 @@
               placeholder="请输入选课码"
             ></b-input>
           </b-field>
-          <a class="button is-link" @click="isCardModalActive = false">新建</a>
+          <a
+            :class="['button', 'is-link', { 'is-loading': submitting }]"
+            @click="newSubject"
+            >新建</a
+          >
         </div>
       </div>
     </b-modal>
@@ -79,7 +93,7 @@
 
 <script>
 import Card from "@/components/Card";
-import { getTeacherCourses } from "@/api/course";
+import { getTeacherCourses, postTeacherCourse } from "@/api/course";
 import {
   required,
   minLength,
@@ -107,14 +121,55 @@ export default {
   data() {
     return {
       courses: [],
-      isCardModalActive: false,
+      newSubjectActive: false,
       username: "",
       coursename: "",
-      coursenum: ""
+      coursenum: "",
+      submitting: false,
+      isServerError: false,
+      errorMassage: "服务器错误,请稍后再试",
+      isTeacherCourseLoading: true
     };
   },
   mounted() {
-    getTeacherCourses().then(value => (this.courses = value.data));
+    this.getCourses();
+  },
+  methods: {
+    getCourses() {
+      getTeacherCourses().then(value => {
+        this.courses = value.data;
+        this.isTeacherCourseLoading = false;
+      });
+    },
+    //新建课程
+    newSubject() {
+      if (this.submitting) return;
+      this.$v.$touch();
+      if (!this.$v.$invalid) {
+        this.isServerError = false;
+        let subjectInfo = {
+          name: this.coursename,
+          code: this.coursenum
+        };
+        this.submitting = true;
+        postTeacherCourse(subjectInfo)
+          .then(res => {
+            this.submitting = false;
+            this.$toast.open({
+              message: `${res.data.name}创建成功`,
+              type: "is-success"
+            });
+            this.newSubjectActive = false;
+            this.getCourses();
+          })
+          .catch(e => {
+            if (this.errorMassage) {
+              this.errorMassage = e.message;
+            }
+            this.isServerError = true;
+          });
+      }
+    }
   }
 };
 </script>
@@ -122,7 +177,6 @@ export default {
 @import "../../../assets/scss/app";
 .container.hint {
   padding: 30px 20px 20px 30px;
-
   /*background-image: linear-gradient(to right, #6a11cb 0%, #2575fc 100%);*/
   /*TODO: 寻找质量更高的图片以防止对比度过低 */
   background-image: url("../../../assets/login_background.png");

+ 100 - 45
src/views/teacher/Question/index.vue

@@ -1,24 +1,28 @@
 <template>
   <div>
-    <page-header :routes="[{ name: `查看题目`, path: 'question/' }]">
+    <page-header
+      :loading="loadingQuestionList"
+      :routes="[{ name: `查看题目`, path: 'question/' }]"
+    >
       <template slot="title">
         查看题目
       </template>
     </page-header>
     <page-body>
+      <page-section-loading :show="loadingQuestionList" />
       <!-- container面板-->
-      <div class="data-table">
+      <div v-if="!loadingQuestionList" class="data-table">
         <!--下拉框&搜索框-->
         <div class="form-group">
           <!--下拉框-->
           <b-field>
             <b-select placeholder="选择题目类型">
               <option
-                v-for="option in questionType"
-                :value="option.title"
-                :key="option.id"
+                v-for="(option, index) in questionType"
+                :value="option"
+                :key="index"
               >
-                {{ option.title }}
+                {{ option }}
               </option>
             </b-select>
           </b-field>
@@ -29,19 +33,15 @@
               placeholder="输入关键字查找题目"
               type="search"
               icon="magnify"
+              v-model="keyWord"
             >
             </b-input>
-            <p class="control"><button class="button is-link">查找</button></p>
+            <p class="control">
+              <button class="button is-link" @click="getQuestionsByKey()">
+                查找
+              </button>
+            </p>
           </b-field>
-
-          <!--<b-field>-->
-          <!--<b-input-->
-          <!--placeholder="输入关键字查找题目"-->
-          <!--type="search"-->
-          <!--icon="magnify"-->
-          <!--&gt;-->
-          <!--</b-input>-->
-          <!--</b-field>-->
         </div>
         <!--题目table-->
         <div class="question-list">
@@ -55,7 +55,10 @@
                 <router-link
                   :to="{
                     path: `${props.row.id}`,
-                    query: { questionName: props.row.name }
+                    query: {
+                      questionName: props.row.name,
+                      questionId: props.row.id
+                    }
                   }"
                   >{{ props.row.name }}</router-link
                 >
@@ -80,11 +83,11 @@
 
               <b-table-column
                 label="平均时长"
-                field="expectTime"
+                field="expect_time"
                 sortable
                 centered
               >
-                {{ props.row.expectTime }}分钟
+                {{ props.row.expect_time }}分钟
               </b-table-column>
             </template>
 
@@ -94,22 +97,27 @@
                   <div class="content">
                     <p class="detail-content">
                       <strong>{{ props.row.name }}</strong>
-                      <small
-                        ><b-tag type="is-info"
-                          >{{ props.row.expectTime }}分钟</b-tag
-                        ></small
-                      >
-                      <small
-                        ><b-tag
+                      <small v-show="props.row.expect_time">
+                        <b-tag type="is-info"
+                          >{{ props.row.expect_time }}分钟</b-tag
+                        >
+                      </small>
+                      <small v-show="props.row.difficulty">
+                        <b-tag
                           :type="`${difficultyClass(props.row.difficulty)}`"
                           >{{ difficultyType(props.row.difficulty) }}</b-tag
-                        ></small
-                      >
-                      <small
-                        ><b-tag type="is-link">{{
-                          props.row.assignmentType.value
-                        }}</b-tag></small
-                      >
+                        >
+                      </small>
+                      <small v-show="props.row.assignmentType">
+                        <b-tag type="is-link">{{
+                          props.row.assignmentType
+                        }}</b-tag>
+                      </small>
+                      <small v-show="props.row.questionType">
+                        <b-tag type="is-primary">{{
+                          props.row.questionType
+                        }}</b-tag>
+                      </small>
                       <br /><br />
                       {{ props.row.description }}
                     </p>
@@ -125,15 +133,20 @@
 </template>
 <script>
 import { mapState } from "vuex";
-import { getStudentCourseReviewList } from "@/api/review";
-import { getTeacherQuestions } from "@/api/question";
+import {
+  getQuestionsByAssignType,
+  getTeacherQuestionType,
+  getTeacherKeywordQuestions
+} from "@/api/question";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 export default {
   components: {
     PageHeader,
-    PageBody
+    PageBody,
+    PageSectionLoading
   },
   computed: {
     ...mapState({
@@ -144,20 +157,62 @@ export default {
     return {
       courses: [],
       review: [],
-      questionType: [
-        { id: 1, title: "文档作业题目" },
-        { id: 2, title: "代码作业题目" }
-      ],
-      data: []
+      questionType: [],
+      data: [],
+      keyWord: "",
+      errorMessage: "服务器错误,请稍后再试",
+      loadingQuestionList: true
     };
   },
   mounted() {
-    getStudentCourseReviewList(this.courses.id).then(
-      value => (this.review = value.data)
-    );
-    getTeacherQuestions().then(value => (this.data = value.data));
+    //获取全部题目分类
+    this.getQuestionType();
+    //获取全部题目
+    this.getQuestions();
+
+    this.loadingQuestionList = false;
   },
   methods: {
+    //获取全部题目分类
+    getQuestionType() {
+      getTeacherQuestionType()
+        .then(value => {
+          this.questionType = value.data;
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message,
+            type: "is-link"
+          });
+        });
+    },
+    //获取全部题目
+    getQuestions() {
+      getQuestionsByAssignType({ type: "DOCUMENT", page: 0, limit: 10 })
+        .then(value => {
+          // this.data = value.res.content;
+          this.data = value.data;
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message,
+            type: "is-danger"
+          });
+        });
+    },
+    //根据关键字查找题目
+    getQuestionsByKey() {
+      getTeacherKeywordQuestions(this.keyWord)
+        .then(value => {
+          this.data = value.data;
+        })
+        .catch(e => {
+          this.$toast.open({
+            message: e.message || this.errorMessage,
+            type: "is-danger"
+          });
+        });
+    },
     difficultyType: level => {
       switch (level) {
         case 1:

+ 38 - 26
src/views/teacher/Question/questionDetail.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <page-header
+      :loading="isQuestionDetailLoading"
       :routes="[
         { name: '查看题目', path: 'question/' },
         { name: `${questionName}`, path: `question/123` }
@@ -19,30 +20,35 @@
       </template>
     </page-header>
     <page-body>
-      <div class="data-table">
-        <div class="question-description">
-          <div class="subtitle">题目描述</div>
-          <div class="question-content">{{ questionInfo.description }}</div>
-        </div>
-        <div class="question-description">
-          <div class="subtitle">预计完成时间</div>
-          <div class="question-content">{{ questionInfo.expectTime }}分钟</div>
-        </div>
-        <div class="question-description">
-          <div class="subtitle">题目难度</div>
-          <div class="question-content">
-            {{ difficultyType(questionInfo.difficulty) }}
+      <page-section-loading :show="isQuestionDetailLoading" />
+      <div v-if="!isQuestionDetailLoading" class="data-table">
+        <div>
+          <div class="question-description">
+            <div class="subtitle">题目描述</div>
+            <div class="question-content">{{ questionInfo.description }}</div>
           </div>
-        </div>
-        <div class="question-description">
-          <div class="subtitle">文件信息</div>
-          <div class="question-content">
-            <div
-              class="file-info"
-              v-for="(item, index) in questionInfo.file"
-              :key="index"
-            >
-              <a>{{ item.filename }}</a>
+          <div class="question-description">
+            <div class="subtitle">预计完成时间</div>
+            <div class="question-content">
+              {{ questionInfo.expectTime }}分钟
+            </div>
+          </div>
+          <div class="question-description">
+            <div class="subtitle">题目难度</div>
+            <div class="question-content">
+              {{ difficultyType(questionInfo.difficulty) }}
+            </div>
+          </div>
+          <div class="question-description">
+            <div class="subtitle">文件信息</div>
+            <div class="question-content">
+              <div
+                class="file-info"
+                v-for="(item, index) in questionInfo.file"
+                :key="index"
+              >
+                <a>{{ item.filename }}</a>
+              </div>
             </div>
           </div>
         </div>
@@ -54,23 +60,29 @@
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import { getQuestionDetail } from "@/api/question";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 export default {
   components: {
     PageBody,
-    PageHeader
+    PageHeader,
+    PageSectionLoading
   },
   mounted() {
     this.questionName = this.$route.query.questionName;
-    getQuestionDetail(234).then(res => {
+    this.questionId = this.$route.query.questionId;
+    getQuestionDetail(this.questionId).then(res => {
       console.log(res.data);
       this.questionInfo = res.data;
+      this.isQuestionDetailLoading = false;
     });
   },
   data() {
     return {
       questionName: "",
-      questionInfo: {}
+      questionInfo: {},
+      questionId: "",
+      isQuestionDetailLoading: true
     };
   },
   methods: {

+ 36 - 25
src/views/teacher/Result/index.vue

@@ -1,13 +1,17 @@
 <template>
   <div>
-    <page-header :routes="[{ name: '成绩管理', path: 'result/' }]">
+    <page-header
+      :loading="isResultLoading"
+      :routes="[{ name: '成绩管理', path: 'result/' }]"
+    >
       <template slot="title">
         全部作业成绩
       </template>
     </page-header>
     <page-body>
+      <page-section-loading :show="isResultLoading" />
       <!-- container面板-->
-      <div class="data-table">
+      <div v-show="!isResultLoading" class="data-table">
         <!--下拉框&搜索框-->
         <div class="form-group">
           <!--下拉框-->
@@ -166,7 +170,9 @@
             </div>
           </div>
         </div>
-        <a class="button is-link" @click="isCardModalActive = false"
+        <a
+          :class="['button', 'is-link', { 'is-loading': submitting }]"
+          @click="confirmPercent"
           >确认调整</a
         >
       </div>
@@ -175,19 +181,20 @@
 </template>
 <script>
 import { mapState } from "vuex";
-// import { getStudentCourseReviewList } from "@/api/review";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import Chart from "chart.js";
 import BInput from "buefy/src/components/input/Input";
 import { required, numeric, between } from "vuelidate/lib/validators";
 import { getStudentFinaleResult } from "@/api/resultstatistics";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 
 export default {
   components: {
     BInput,
     PageHeader,
-    PageBody
+    PageBody,
+    PageSectionLoading
   },
   validations: {
     percent: {
@@ -206,15 +213,15 @@ export default {
       name: "",
       courses: [],
       review: [],
-      questionType: [
-        { id: 1, title: "文档作业题目" },
-        { id: 2, title: "代码作业题目" }
-      ],
       data: [],
       isCardModalActive: false,
       myChart2: {},
       percent: "",
-      finalResultPercentage: [20, 20, 20, 10, 10, 10, 10]
+      finalResultPercentage: [20, 20, 20, 10, 10, 10, 10],
+      submitting: false,
+      isServerError: false,
+      errorMessage: "服务器错误,请稍后重试",
+      isResultLoading: true
     };
   },
   methods: {
@@ -256,6 +263,13 @@ export default {
         currentIndex++;
       }
       return parseInt(finalRes);
+    },
+    confirmPercent() {
+      this.submitting = true;
+      this.$toast.open({
+        message: "成绩比例调整成功",
+        type: "is-success"
+      });
     }
   },
   mounted() {
@@ -263,19 +277,22 @@ export default {
     getStudentFinaleResult(this.courses.id).then(res => {
       console.log(res.data);
       this.data = res.data;
+      this.isResultLoading = false;
     });
     //绘制图表
-    var resultChart = document.getElementById("result-chart");
-    var myChart2 = new Chart(resultChart, {
+    let resultChart = document.getElementById("result-chart");
+    let myChart2 = new Chart(resultChart, {
       type: "bar",
       data: {
         labels: [
-          "0-30分段",
-          "30-60分段",
-          "60-70分段",
-          "70-80分段",
-          "80-90分段",
-          "90-100分段"
+          "0分",
+          "1-29分",
+          "30-59分段",
+          "60-69分段",
+          "70-79分段",
+          "80-89分段",
+          "90-99分段",
+          "100分"
         ],
         datasets: [
           {
@@ -285,7 +302,7 @@ export default {
             borderWidth: 1,
             pointStrokeColor: "#fff",
             pointStyle: "crossRot",
-            data: [6, 10, 34, 50, 80, 30],
+            data: [2, 6, 10, 34, 50, 80, 30, 2],
             cubicInterpolationMode: "monotone",
             spanGaps: "false",
             fill: "false"
@@ -303,12 +320,6 @@ export default {
   /*color: #7957d5;*/
   font-weight: bold;
 }
-/*.data-table {*/
-/*width: 100%;*/
-/*padding: 30px;*/
-/*!*min-height: 80vh;*!*/
-/*background-color: #ffffff;*/
-/*}*/
 .chart {
   margin-top: 1.5rem;
 }

+ 72 - 54
src/views/teacher/Review/ReviewDetail/Detail/index.vue

@@ -1,67 +1,80 @@
 <template>
   <div>
-    <div class="page-section">
-      <div class="columns is-desktop">
-        <div class="column">
-          <h5 class="title is-5">id:</h5>
-          <div class="subtitle is-6">{{ detail.reviewId }}</div>
-        </div>
-        <div class="column">
-          <h5 class="title is-5">名称:</h5>
-          <div class="subtitle is-6">{{ detail.name }}</div>
-        </div>
-        <div class="column">
-          <h5 class="title is-5">状态:</h5>
-          <div class="subtitle is-6">
-            <review-state-tag :state="detail.state" />
-          </div>
+    <div v-if="detailLoading">
+      <page-section-loading show />
+      <div class="space">
+        <div class="columns is-desktop">
+          <div class="column"><page-section-loading show /></div>
+          <div class="column"><page-section-loading show /></div>
         </div>
       </div>
-      <div class="columns is-desktop">
-        <div class="column">
-          <h5 class="title is-5">互评结束时间:</h5>
-          <div class="subtitle is-6">
-            {{ displayUnixTime(detail.reviewDdl) }}
+    </div>
+    <div v-else>
+      <div class="page-section">
+        <div class="columns is-desktop">
+          <div class="column">
+            <h5 class="title is-5">id:</h5>
+            <div class="subtitle is-6">{{ detail.reviewId }}</div>
           </div>
-        </div>
-        <div class="column">
-          <h5 class="title is-5">重申申请结束时间:</h5>
-          <div class="subtitle is-6">
-            {{ displayUnixTime(detail.argueDdl) }}
+          <div class="column">
+            <h5 class="title is-5">名称:</h5>
+            <div class="subtitle is-6">{{ detail.name }}</div>
+          </div>
+          <div class="column">
+            <h5 class="title is-5">状态:</h5>
+            <div class="subtitle is-6">
+              <review-state-tag :state="detail.state" />
+            </div>
           </div>
         </div>
-        <div class="column">
-          <h5 class="title is-5">重申结束时间:</h5>
-          <div class="subtitle is-6">{{ displayUnixTime(detail.endDdl) }}</div>
+        <div class="columns is-desktop">
+          <div class="column">
+            <h5 class="title is-5">互评结束时间:</h5>
+            <div class="subtitle is-6">
+              {{ displayUnixTime(detail.reviewDdl) }}
+            </div>
+          </div>
+          <div class="column">
+            <h5 class="title is-5">重申申请结束时间:</h5>
+            <div class="subtitle is-6">
+              {{ displayUnixTime(detail.argueDdl) }}
+            </div>
+          </div>
+          <div class="column">
+            <h5 class="title is-5">重申结束时间:</h5>
+            <div class="subtitle is-6">
+              {{ displayUnixTime(detail.endDdl) }}
+            </div>
+          </div>
         </div>
       </div>
-    </div>
-    <div class="space">
-      <div class="columns is-desktop">
-        <div class="column">
-          <div class="page-section">
-            <h5 class="title is-5">完成率: {{ sum.completeRate }}%</h5>
-            <progress
-              class="progress is-success"
-              :value="sum.completeRate"
-              max="100"
-              >{sum.completeRate}%</progress
-            >
+      <div class="space">
+        <div class="columns is-desktop">
+          <div class="column">
+            <div class="page-section">
+              <h5 class="title is-5">完成率: {{ sum.completeRate }}%</h5>
+              <progress
+                class="progress is-success"
+                :value="sum.completeRate"
+                max="100"
+                >{sum.completeRate}%</progress
+              >
+            </div>
           </div>
-        </div>
-        <div class="column">
-          <div class="page-section">
-            <h5 class="title is-5">checkList 通过情况:</h5>
-            <div
-              class="checklist-item"
-              :key="item.id"
-              v-for="item in sum.checklistItems"
-            >
-              <div>id: {{ item.id }}</div>
-              <b-tag type="is-success" style="margin-right: 5px"
-                >通过 : {{ item.passedNum }}</b-tag
+          <div class="column">
+            <div class="page-section">
+              <h5 class="title is-5">checkList 通过情况:</h5>
+              <div
+                class="checklist-item"
+                :key="item.id"
+                v-for="item in sum.checklistItems"
               >
-              <span>{{ item.content }}</span>
+                <div>id: {{ item.id }}</div>
+                <b-tag type="is-success" style="margin-right: 5px"
+                  >通过 : {{ item.passedNum }}</b-tag
+                >
+                <span>{{ item.content }}</span>
+              </div>
             </div>
           </div>
         </div>
@@ -74,13 +87,18 @@
 import ReviewStateTag from "@/components/ReviewStateTag/index";
 import timeMixins from "@/mixins/time";
 import { getTeacherReviewResult } from "@/api/review";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 export default {
-  components: { ReviewStateTag },
+  components: { PageSectionLoading, ReviewStateTag },
   mixins: [timeMixins],
   props: {
     detail: {
       type: Object,
       default: () => ({ completeRate: 0 })
+    },
+    detailLoading: {
+      type: Boolean,
+      default: false
     }
   },
   data() {

+ 19 - 5
src/views/teacher/Review/ReviewDetail/GroupDocDetail/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <page-header
+      :loading="groupDetailLoading"
       :routes="[
         { name: '互评作业', path: 'review' },
         { name: '文档详情', path: `review/${$route.params.reviewId}` },
@@ -24,7 +25,8 @@
     <page-body>
       <div class="columns">
         <div class="column is-8">
-          <div class="page-section">
+          <div v-if="detailLoading"><page-section-loading show /></div>
+          <div v-else class="page-section">
             <div v-if="detail.docContent" class="content">
               <vue-markdown>{{ detail.docContent }}</vue-markdown>
             </div>
@@ -32,7 +34,9 @@
         </div>
         <div class="column is-4">
           <div class="right-part">
+            <div v-if="detailLoading"><page-section-loading show /></div>
             <result-check-list-item
+              v-else
               v-for="item in detail.checkListResult"
               :key="item.id"
               :check-result-item="item"
@@ -51,8 +55,10 @@ import ResultCheckListItem from "@/components/CheckList/ResultCheckListItem";
 import PageHeader from "@/components/PageHeader";
 import PageBody from "@/components/PageBody/index";
 import { getGroupDetail } from "@/api/group";
+import PageSectionLoading from "@/components/PageBody/PageSectionLoading/index";
 export default {
   components: {
+    PageSectionLoading,
     PageBody,
     PageHeader,
     ResultCheckListItem,
@@ -64,17 +70,25 @@ export default {
         review: {},
         checkListResult: []
       },
+      detailLoading: false,
       groupDetail: {},
+      groupDetailLoading: false,
       submitLoading: false
     };
   },
   mixins: [timeMixins],
   mounted() {
     const { reviewId, groupId } = this.$route.params;
-    getGroupDetail(groupId).then(value => (this.groupDetail = value.data));
-    getTeacherReviewGroupDetail(reviewId, groupId).then(
-      resp => (this.detail = resp.data)
-    );
+    this.detailLoading = true;
+    this.groupDetailLoading = true;
+    getGroupDetail(groupId).then(value => {
+      this.groupDetail = value.data;
+      this.groupDetailLoading = false;
+    });
+    getTeacherReviewGroupDetail(reviewId, groupId).then(resp => {
+      this.detail = resp.data;
+      this.detailLoading = false;
+    });
   }
 };
 </script>

+ 8 - 0
src/views/teacher/Review/ReviewDetail/Groups/index.vue

@@ -5,7 +5,11 @@
       </b-input>
     </div>
     <div class="columns is-multiline is-desktop">
+      <div v-if="groupsLoading" class="column is-4-desktop">
+        <card loading />
+      </div>
       <div
+        v-else
         :key="item.id"
         v-for="item in availableGroups"
         class="column is-4-desktop"
@@ -28,6 +32,10 @@ export default {
     groups: {
       type: Array,
       default: () => []
+    },
+    groupsLoading: {
+      type: Boolean,
+      default: false
     }
   },
   data() {

+ 21 - 3
src/views/teacher/Review/ReviewDetail/index.vue

@@ -1,6 +1,7 @@
 <template>
   <div>
     <page-header
+      :loading="detailLoading"
       :routes="[{ name: '互评作业', path: 'review' }, { name: '文档详情' }]"
       :tab-list="[
         { name: '作业详情', path: `review/${$route.params.reviewId}` },
@@ -30,7 +31,14 @@
         </div>
       </template>
     </page-header>
-    <page-body> <router-view :detail="detail" :groups="groups" /> </page-body>
+    <page-body>
+      <router-view
+        :detail="detail"
+        :groups="groups"
+        :detail-loading="detailLoading"
+        :groups-loading="groupsLoading"
+      />
+    </page-body>
     <b-modal :active.sync="showModal" :width="400">
       <div class="card">
         <div class="title">每个等级通过数字</div>
@@ -80,7 +88,9 @@ export default {
   data() {
     return {
       detail: {},
+      detailLoading: false,
       groups: [],
+      groupsLoading: false,
       showModal: false,
       grade: {
         A: 0,
@@ -96,8 +106,16 @@ export default {
   mixins: [timeMixins],
   mounted() {
     const { reviewId } = this.$route.params;
-    getTeacherReviewDetail(reviewId).then(resp => (this.detail = resp.data));
-    getTeacherReviewAllGroups(reviewId).then(resp => (this.groups = resp.data));
+    this.detailLoading = true;
+    this.groupsLoading = true;
+    getTeacherReviewDetail(reviewId).then(resp => {
+      this.detail = resp.data;
+      this.detailLoading = false;
+    });
+    getTeacherReviewAllGroups(reviewId).then(resp => {
+      this.groups = resp.data;
+      this.groupsLoading = false;
+    });
   },
   methods: {
     async submit() {

+ 14 - 2
src/views/teacher/Review/index.vue

@@ -1,6 +1,9 @@
 <template>
   <div>
-    <page-header :routes="[{ name: '互评作业', path: 'review' }]">
+    <page-header
+      :loading="reviewListLoading"
+      :routes="[{ name: '互评作业', path: 'review' }]"
+    >
       <template slot="title">
         互评作业
       </template>
@@ -15,7 +18,15 @@
     </page-header>
     <page-body>
       <div class="columns is-multiline is-desktop">
-        <div :key="item.id" v-for="item in review" class="column is-4-desktop">
+        <div v-if="reviewListLoading" class="column is-4-desktop">
+          <card loading />
+        </div>
+        <div
+          v-else
+          :key="item.id"
+          v-for="item in review"
+          class="column is-4-desktop"
+        >
           <router-link :to="`${$route.path}/${item.reviewId}`">
             <card hoverable>
               {{ item.name }}
@@ -144,6 +155,7 @@ export default {
     return {
       courses: [],
       review: [],
+      reviewListLoading: false,
       showModal: false,
       submitting: false,
       docHomework: [],

+ 1 - 0
vue.config.js

@@ -3,6 +3,7 @@ module.exports = {
     proxy: {
       "^/api": {
         target: "http://localhost:4000/",
+        // target: "http://10.1.1.198:8080/",
         ws: true,
         changeOrigin: true
       }