Selaa lähdekoodia

[新增] 老师互评部分

WuXinyu 7 vuotta sitten
vanhempi
commit
bd75f31af6

+ 6 - 0
mock/modules/group/index.js

@@ -20,4 +20,10 @@ router
     })
   );
 
+router.route("/:groupId").get((req, res) =>
+  res.send({
+    data: groupSerializer(req.params.groupId)
+  })
+);
+
 module.exports = router;

+ 2 - 2
mock/serializers/course/GroupSerializer.js

@@ -14,9 +14,9 @@ const userSerializer = require("../user/UserSerializer");
  * @description 生成课程信息
  * @returns {GroupSerializerType}
  */
-module.exports = () => {
+module.exports = (id = makeId()) => {
   return {
-    id: makeId(),
+    id,
     name: "tag-maker",
     code: "xxxxx",
     members: [

+ 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/group.js

@@ -39,3 +39,12 @@ export const putStudentGroup = code => {
     }
   });
 };
+
+/**
+ * 获得小组详情
+ * @param groupId
+ * @returns {Promise<{data:GroupSerializerType}>}
+ */
+export const getGroupDetail = groupId => {
+  return request(`${GROUP_MODULE}/${groupId}`);
+};

+ 8 - 0
src/layouts/TeacherMainLayout.vue

@@ -0,0 +1,8 @@
+<template>
+  <div>
+    <div></div>
+    <router-view></router-view>
+  </div>
+</template>
+
+<style lang="scss"></style>

+ 1 - 0
src/mixins/time.js

@@ -3,6 +3,7 @@ import dayjs from "dayjs";
 export default {
   methods: {
     displayUnixTime(time) {
+      if (!time) return;
       return dayjs.unix(time).format("YYYY年 MM月 DD日 HH:mm:ss");
     }
   }

+ 25 - 3
src/router/routes.js

@@ -99,11 +99,33 @@ export default [
       },
       {
         path: "review",
-        component: () => import("@/views/teacher/Review/assignmentList")
+        component: () => import("@/views/teacher/Review")
       },
       {
-        path: "review/:id",
-        component: () => import("@/views/teacher/Review/assignment")
+        path: "review/:reviewId",
+        component: () => import("@/views/teacher/Review/ReviewDetail"),
+        children: [
+          {
+            path: "",
+            component: () =>
+              import("@/views/teacher/Review/ReviewDetail/Detail")
+          },
+          {
+            path: "groups",
+            component: () =>
+              import("@/views/teacher/Review/ReviewDetail/Groups")
+          }
+        ]
+      },
+      {
+        path: "review/:reviewId/groups/:groupId",
+        component: () =>
+          import("@/views/teacher/Review/ReviewDetail/GroupDocDetail")
+      },
+      {
+        path: "review/:reviewId/groups/:groupId/argue",
+        component: () =>
+          import("@/views/teacher/Review/ReviewDetail/GroupArgueDetail")
       }
     ]
   },

+ 114 - 0
src/views/teacher/Review/ReviewDetail/Detail/index.vue

@@ -0,0 +1,114 @@
+<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>
+      </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 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 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
+              >
+              <span>{{ item.content }}</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import ReviewStateTag from "@/components/ReviewStateTag/index";
+import timeMixins from "@/mixins/time";
+import { getTeacherReviewResult } from "@/api/review";
+export default {
+  components: { ReviewStateTag },
+  mixins: [timeMixins],
+  props: {
+    detail: {
+      type: Object,
+      default: () => ({ completeRate: 0 })
+    }
+  },
+  data() {
+    return {
+      sum: { passedCDF: [], checklistItems: [] }
+    };
+  },
+  mounted() {
+    getTeacherReviewResult(this.$route.params.reviewId).then(
+      value => (this.sum = value.data)
+    );
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../../assets/scss/app";
+.divider {
+  margin: $default-margin 0;
+  height: 1px;
+  background-color: #eaeaea;
+}
+
+.space {
+  padding-top: $default-margin-between;
+}
+
+.checklist-item {
+  margin: $default-margin 0;
+}
+</style>

+ 121 - 0
src/views/teacher/Review/ReviewDetail/GroupArgueDetail/index.vue

@@ -0,0 +1,121 @@
+<template>
+  <div>
+    <page-header
+      :routes="[
+        { name: '互评作业', path: 'review' },
+        { name: '文档详情', path: `review/${$route.params.reviewId}` },
+        {
+          name: '小组作业',
+          path: `review/${$route.params.reviewId}/groups/${
+            $route.params.groupId
+          }`
+        },
+        {
+          name: '重评作业'
+        }
+      ]"
+    >
+      <template slot="title">
+        {{ groupDetail.name }}
+      </template>
+      <template slot="action">
+        <router-link
+          :to="$route.path.substring(0, $route.path.length - '/argue'.length)"
+          class="button is-link"
+        >
+          返回小组详情
+        </router-link>
+      </template>
+    </page-header>
+    <page-body>
+      <div class="columns">
+        <div class="column is-8">
+          <div class="page-section">
+            <div v-if="detail.docContent" class="content">
+              <vue-markdown>{{ detail.docContent }}</vue-markdown>
+            </div>
+          </div>
+        </div>
+        <div class="column is-4">
+          <div class="right-part">
+            <div
+              class="button is-medium is-primary action-button"
+              :class="{ 'is-loading': submitLoading }"
+              @click="submitCheckList"
+            >
+              提 交
+            </div>
+            <check-list v-model="detail.checkList" />
+          </div>
+        </div>
+      </div>
+    </page-body>
+  </div>
+</template>
+<script>
+import VueMarkdown from "vue-markdown";
+import { getTeacherStartArgue, postTeacherArgueResult } from "@/api/review";
+import timeMixins from "@/mixins/time";
+import PageHeader from "@/components/PageHeader";
+import PageBody from "@/components/PageBody/index";
+import { getGroupDetail } from "@/api/group";
+import CheckList from "@/components/CheckList/index";
+export default {
+  components: {
+    PageBody,
+    PageHeader,
+    CheckList,
+    VueMarkdown
+  },
+  data() {
+    return {
+      detail: {
+        review: {},
+        checkList: []
+      },
+      groupDetail: {},
+      submitLoading: false
+    };
+  },
+  mixins: [timeMixins],
+  mounted() {
+    const { reviewId, groupId } = this.$route.params;
+    getGroupDetail(groupId).then(value => (this.groupDetail = value.data));
+    getTeacherStartArgue(reviewId, groupId).then(
+      resp => (this.detail = resp.data)
+    );
+  },
+  methods: {
+    submitCheckList() {
+      const { reviewId, groupId } = this.$route.params;
+      this.submitLoading = true;
+      postTeacherArgueResult(reviewId, groupId, this.detail.checkList).then(
+        resp => {
+          this.submitLoading = false;
+          this.detail.checkList = resp.checkList;
+        }
+      );
+    }
+  }
+};
+</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>

+ 100 - 0
src/views/teacher/Review/ReviewDetail/GroupDocDetail/index.vue

@@ -0,0 +1,100 @@
+<template>
+  <div>
+    <page-header
+      :routes="[
+        { name: '互评作业', path: 'review' },
+        { name: '文档详情', path: `review/${$route.params.reviewId}` },
+        {
+          name: '小组作业',
+          path: `review/${$route.params.reviewId}/groups/${
+            $route.params.groupId
+          }`
+        }
+      ]"
+    >
+      <template slot="title">
+        {{ groupDetail.name }}
+      </template>
+      <template slot="action">
+        <router-link :to="`${$route.path}/argue`" class="button is-primary">
+          开始重评
+        </router-link>
+      </template>
+    </page-header>
+    <page-body>
+      <div class="columns">
+        <div class="column is-8">
+          <div class="page-section">
+            <div v-if="detail.docContent" class="content">
+              <vue-markdown>{{ detail.docContent }}</vue-markdown>
+            </div>
+          </div>
+        </div>
+        <div class="column is-4">
+          <div class="right-part">
+            <result-check-list-item
+              v-for="item in detail.checkListResult"
+              :key="item.id"
+              :check-result-item="item"
+            />
+          </div>
+        </div>
+      </div>
+    </page-body>
+  </div>
+</template>
+<script>
+import VueMarkdown from "vue-markdown";
+import { getTeacherReviewGroupDetail } from "@/api/review";
+import timeMixins from "@/mixins/time";
+import ResultCheckListItem from "@/components/CheckList/ResultCheckListItem";
+import PageHeader from "@/components/PageHeader";
+import PageBody from "@/components/PageBody/index";
+import { getGroupDetail } from "@/api/group";
+export default {
+  components: {
+    PageBody,
+    PageHeader,
+    ResultCheckListItem,
+    VueMarkdown
+  },
+  data() {
+    return {
+      detail: {
+        review: {},
+        checkListResult: []
+      },
+      groupDetail: {},
+      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)
+    );
+  }
+};
+</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>

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

@@ -0,0 +1,51 @@
+<template>
+  <div>
+    <div class="input-area">
+      <b-input v-model="filter" size="is-medium" placeholder="输入小组名来搜索">
+      </b-input>
+    </div>
+    <div class="columns is-multiline is-desktop">
+      <div :key="item.id" v-for="item in availableGroups" class="column is-4">
+        <router-link :to="`${$route.path}/${item.id}`">
+          <card hoverable> {{ item.name }} </card>
+        </router-link>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import Card from "@/components/Card";
+export default {
+  components: {
+    Card
+  },
+  props: {
+    groups: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      filter: ""
+    };
+  },
+  computed: {
+    availableGroups() {
+      if (this.filter === "") return this.groups;
+
+      return this.groups.filter(group => group.name.indexOf(this.filter) >= 0);
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../../assets/scss/app";
+.input-area {
+  margin: 0 auto;
+  max-width: 600px;
+  padding-bottom: $default-margin-between;
+}
+</style>

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

@@ -0,0 +1,142 @@
+<template>
+  <div>
+    <page-header
+      :routes="[{ name: '互评作业', path: 'review' }, { name: '文档详情' }]"
+      :tab-list="[
+        { name: '作业详情', path: `review/${$route.params.reviewId}` },
+        { name: '小组列表', path: `review/${$route.params.reviewId}/groups` }
+      ]"
+    >
+      <template slot="title">
+        {{ detail.name }}
+      </template>
+      <template slot="content">
+        <div>共有 {{ groups.length }} 组小组参与互评</div>
+      </template>
+      <template slot="action">
+        <div @click="() => (this.showModal = true)" class="button is-primary">
+          制定评分标准
+        </div>
+      </template>
+      <template slot="extraContent">
+        <div class="tag is-light" v-if="detail.state === 'REVIEWING'">
+          互评结束时间 : {{ displayUnixTime(detail.reviewDdl) }}
+        </div>
+        <div class="tag is-light" v-if="detail.state === 'ARGUING'">
+          申诉结束时间 : {{ displayUnixTime(detail.argueDdl) }}
+        </div>
+        <div class="tag is-light" v-if="detail.state === 'SOLVING'">
+          处理申诉结束时间 : {{ displayUnixTime(detail.endDdl) }}
+        </div>
+      </template>
+    </page-header>
+    <page-body> <router-view :detail="detail" :groups="groups" /> </page-body>
+    <b-modal :active.sync="showModal" :width="400">
+      <div class="card">
+        <div class="title">每个等级通过数字</div>
+        <div class="content">
+          <b-field label="A">
+            <b-input type="number" v-model="grade.A"></b-input>
+          </b-field>
+          <b-field label="B">
+            <b-input type="number" v-model="grade.B"></b-input>
+          </b-field>
+          <b-field label="C">
+            <b-input type="number" v-model="grade.C"></b-input>
+          </b-field>
+          <b-field label="D">
+            <b-input type="number" v-model="grade.D"></b-input>
+          </b-field>
+          <b-field label="E">
+            <b-input type="number" v-model="grade.E"></b-input>
+          </b-field>
+          <b-field label="F">
+            <b-input type="number" v-model="grade.F"></b-input>
+          </b-field>
+
+          <a
+            class="button is-link"
+            :class="{ 'is-loading': submitLoading }"
+            @click="submit()"
+          >
+            更新评分标准
+          </a>
+        </div>
+      </div>
+    </b-modal>
+  </div>
+</template>
+<script>
+import PageHeader from "@/components/PageHeader";
+import PageBody from "@/components/PageBody/index";
+import {
+  getTeacherReviewAllGroups,
+  getTeacherReviewDetail,
+  postReviewSumLevel
+} from "@/api/review";
+import timeMixins from "@/mixins/time";
+export default {
+  components: { PageBody, PageHeader },
+  data() {
+    return {
+      detail: {},
+      groups: [],
+      showModal: false,
+      grade: {
+        A: 0,
+        B: 0,
+        C: 0,
+        D: 0,
+        E: 0,
+        F: 0
+      },
+      submitLoading: false
+    };
+  },
+  mixins: [timeMixins],
+  mounted() {
+    const { reviewId } = this.$route.params;
+    getTeacherReviewDetail(reviewId).then(resp => (this.detail = resp.data));
+    getTeacherReviewAllGroups(reviewId).then(resp => (this.groups = resp.data));
+  },
+  methods: {
+    async submit() {
+      const { reviewId } = this.$route.params;
+      this.submitLoading = true;
+      try {
+        await postReviewSumLevel(reviewId, this.grade);
+      } catch (e) {
+        this.$toast.open({
+          message: e.message,
+          type: "is-error"
+        });
+      }
+      this.submitLoading = false;
+      this.showModal = false;
+      this.$toast.open({
+        message: "制定成功",
+        type: "is-success"
+      });
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../assets/scss/app";
+.review-body {
+  display: flex;
+
+  &__left {
+    flex-grow: 1;
+  }
+
+  &__right {
+    min-width: 300px;
+  }
+}
+.card {
+  padding: $default-margin-between;
+  border-radius: $default-radius;
+}
+</style>

+ 0 - 142
src/views/teacher/Review/assignmentList.vue

@@ -1,142 +0,0 @@
-<template>
-  <div>
-    <page-header :routes="[{ name: '互评作业', path: 'review' }]">
-      <template slot="title">
-        全部互评作业
-      </template>
-      <template slot="content">
-        当前有3份互评作业进行中
-      </template>
-    </page-header>
-    <page-body>
-      <div class="page-section">
-        <!--<div class="submit-state">-->
-        <!--<span>提交情况</span>-->
-        <!--<progress class="progress is-link" value="30" max="100">40%</progress>-->
-        <!--50/200-->
-        <!--</div>-->
-        <div class="homework-list">
-          <section>
-            <b-tabs type="is-boxed">
-              <b-tab-item label="进行中的作业">
-                <router-link
-                  :to="`/course-teacher/${this.course.id}/review/123`"
-                  ><b-table
-                    :data="homeworkList"
-                    :columns="columns"
-                    :hoverable="true"
-                  ></b-table
-                ></router-link>
-              </b-tab-item>
-              <b-tab-item label="已结束作业">
-                <div class="info">暂无已结束作业</div>
-              </b-tab-item>
-            </b-tabs>
-          </section>
-        </div>
-      </div>
-    </page-body>
-  </div>
-</template>
-<script>
-import { mapState } from "vuex";
-import PageHeader from "@/components/PageHeader";
-import PageBody from "@/components/PageBody/index";
-const homeworkList = [
-  {
-    id: 1,
-    name: "物流系统需求规格说明文档",
-    beginAt: "2018-12-29",
-    endAt: "2019-01-06",
-    group: 2
-  },
-  {
-    id: 2,
-    name: "物流系统用例图",
-    beginAt: "2018-12-29",
-    endAt: "2019-01-06",
-    group: 3
-  },
-  {
-    id: 3,
-    name: "物流系统体系架构文档",
-    beginAt: "2018-12-29",
-    endAt: "2019-01-06",
-    group: 4
-  }
-];
-
-const columns = [
-  {
-    field: "id",
-    label: "ID",
-    numeric: true
-  },
-  {
-    field: "name",
-    label: "作业名",
-    centered: true
-  },
-  {
-    field: "beginAt",
-    label: "开始时间",
-    centered: true
-  },
-  {
-    field: "endAt",
-    label: "结束时间",
-    centered: true
-  }
-];
-
-export default {
-  components: {
-    PageBody,
-    PageHeader
-  },
-  computed: {
-    ...mapState({
-      course: state => state.course.currentCourse
-    })
-  },
-  data() {
-    return {
-      homeworkList,
-      columns
-    };
-  },
-  mounted() {}
-};
-</script>
-<style lang="scss" scoped>
-.breadcrumb a {
-  font-weight: bold;
-}
-.data-table {
-  width: 100%;
-  padding: 30px;
-  /*min-height: 80vh;*/
-  background-color: #ffffff;
-  .submit-state {
-    display: flex;
-    align-items: center;
-    font-size: 1rem;
-    font-weight: bold;
-    span {
-      margin-right: 1.5rem;
-    }
-    progress {
-      width: 50%;
-      margin-right: 1.5rem;
-    }
-  }
-  .homework-list {
-    .info {
-      display: flex;
-      justify-content: center;
-      margin: 0.75rem;
-      font-weight: bold;
-    }
-  }
-}
-</style>

+ 63 - 0
src/views/teacher/Review/index.vue

@@ -0,0 +1,63 @@
+<template>
+  <div>
+    <page-header :routes="[{ name: '互评作业', path: 'review' }]">
+      <template slot="title">
+        互评作业
+      </template>
+      <template slot="content">
+        共有 {{ review.length }} 份互评作业
+      </template>
+    </page-header>
+    <page-body>
+      <div class="columns is-multiline is-desktop">
+        <div :key="item.id" v-for="item in review" class="column is-4">
+          <router-link :to="`${$route.path}/${item.reviewId}`">
+            <card hoverable>
+              {{ item.name }}
+              <div><review-state-tag :state="item.state" /></div>
+            </card>
+          </router-link>
+        </div>
+      </div>
+    </page-body>
+  </div>
+</template>
+
+<script>
+import { mapState } from "vuex";
+import { getTeacherCourseReviewList } from "@/api/review";
+import Card from "@/components/Card";
+import PageHeader from "@/components/PageHeader";
+import PageBody from "@/components/PageBody/index";
+import ReviewStateTag from "@/components/ReviewStateTag/index";
+export default {
+  components: {
+    ReviewStateTag,
+    PageBody,
+    PageHeader,
+    Card
+  },
+  computed: {
+    ...mapState({
+      course: state => state.course.currentCourse
+    })
+  },
+  data() {
+    return {
+      courses: [],
+      review: []
+    };
+  },
+  mounted() {
+    getTeacherCourseReviewList(this.courses.id).then(
+      value => (this.review = value.data)
+    );
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.top-nav-section {
+  padding: 0 40px;
+}
+</style>