Переглянути джерело

创建考试+考试列表

ChengYuXuan 5 роки тому
батько
коміт
77568100c3

+ 1 - 0
package.json

@@ -12,6 +12,7 @@
     "@mdi/js": "^5.8.55",
     "axios": "^0.21.1",
     "core-js": "^3.6.5",
+    "dayjs": "^1.10.4",
     "echarts": "^5.0.1",
     "marked": "^1.2.7",
     "monaco-editor": "^0.21.2",

+ 3 - 2
src/api/exam.ts

@@ -9,11 +9,12 @@ import {
   LocalDateTime,
   ExamState,
   PageSerializer,
-  ExamListSerializer
+  ExamListSerializer,
+  CreateExamResponse
 } from '@/api/types'
 
 export const createExam = (exam: ExamSerializer) => {
-  return axios.post<ReceivedData>(`${EXAM_MODULE}`, exam)
+  return axios.post<ReceivedData<CreateExamResponse>>(`${EXAM_MODULE}`, exam)
 }
 
 export const getAllExamList = (page: Pageable) => {

+ 7 - 1
src/api/types.d.ts

@@ -41,12 +41,18 @@ export interface ExamSerializer {
   skills: Array<string>
   startTime: LocalDateTime
   examName: string
-  // 上边是创建考试所需的信息
+  // 上边是创建评测所需的信息
   examId?: number
   inviteCode?: string
   joined?: boolean
 }
 
+export interface CreateExamResponse {
+  difficulty: number
+  examId: number
+  inviteCode: string
+}
+
 export interface ExamListSerializer {
   examVOList: Array<ExamSerializer>
   totalNum: number

+ 2 - 2
src/mock/api/user/_examId/get.json

@@ -1,5 +1,5 @@
 {
   "code": "00000",
-  "result": "@EXAM_USERS_SERIALIZER",
-  "msg": "00000"
+  "result|10-100": ["@USER_SERIALIZER"],
+  "msg": ""
 }

+ 0 - 6
src/router/exam.ts

@@ -28,12 +28,6 @@ const routes: Array<RouteConfig> = [
             name: 'code'
           }
         ]
-      },
-      {
-        path: 'result',
-        name: 'result',
-        component: () =>
-          import(/*webpackChunkName: "exam"*/ '@/views/exam/PersonalResult.vue')
       }
     ]
   }

+ 6 - 0
src/router/student.ts

@@ -32,6 +32,12 @@ const routes: Array<RouteConfig> = [
           import(
             /* webpackChunkName: "student" */ '@/views/student/StudentExamList.vue'
           )
+      },
+      {
+        path: 'result/:id',
+        name: 'result',
+        component: () =>
+          import(/*webpackChunkName: "exam"*/ '@/views/exam/PersonalResult.vue')
       }
     ]
   }

+ 9 - 1
src/router/teacher.ts

@@ -19,7 +19,7 @@ const routes: Array<RouteConfig> = [
           )
       },
       {
-        path: 'exams',
+        path: 'created-exams',
         name: 'created-exams',
         component: () =>
           import(
@@ -34,6 +34,14 @@ const routes: Array<RouteConfig> = [
           import(
             /* webpackChunkName: "teacher" */ '@/views/teacher/ExamCreate.vue'
           )
+      },
+      {
+        path: 'exam/:examId',
+        name: 'edit-exams',
+        component: () =>
+          import(
+            /* webpackChunkName: "teacher" */ '@/views/teacher/EditExam.vue'
+          )
       }
     ]
   }

+ 3 - 2
src/store/index.ts

@@ -13,7 +13,7 @@ export type RootState = {
 export const state = (): RootState => ({
   user: (null as unknown) as UserSerializer,
   token:
-    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwLnNlZWNvZGVyLmNuIiwic3ViIjoiMTU5OTYyMzEzMTMiLCJhdWQiOiJzZWVjLXBvcnRhbCIsImF1dGhfdGltZSI6MTYxMTkyNTY0MywiaWF0IjoxNjExOTI1NjQzLCJleHAiOjE2MTIwMTIwNDMsInVzZXJfaW5mbyI6eyJwaG9uZSI6IjE1OTk2MjMxMzEzIiwiZW1haWwiOiIxNjExNTAwMDlAc21haWwubmp1LmVkdS5jbiIsImlkIjozMzIsIm5hbWUiOiIxNjExNTAwMDkiLCJyb2xlIjoiU1RVREVOVCJ9fQ.f7wA_0JWmbDW-B4xSWuPyptSrMS1bf-7xvJmdb5hRNQ'
+    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwLnNlZWNvZGVyLmNuIiwic3ViIjoiMTU5OTYyMzEzMTMiLCJhdWQiOiJzZWVjLXBvcnRhbCIsImF1dGhfdGltZSI6MTYxMjAxMjk1MCwiaWF0IjoxNjEyMDEyOTUwLCJleHAiOjE2MTIwOTkzNTAsInVzZXJfaW5mbyI6eyJwaG9uZSI6IjE1OTk2MjMxMzEzIiwiZW1haWwiOiIxNjExNTAwMDlAc21haWwubmp1LmVkdS5jbiIsImlkIjozMzIsIm5hbWUiOiIxNjExNTAwMDkiLCJyb2xlIjoiU1RVREVOVCJ9fQ.qLCB7yQnH1_ZLzl5Ir_9LuDYmn6xQk7ZRlFxywMKYgc'
 })
 
 export const mutations: MutationTree<RootState> = {
@@ -33,5 +33,6 @@ export default new Vuex.Store({
   mutations: mutations,
   actions: {},
   modules: {},
-  plugins: [createPersistedState()]
+  plugins: [createPersistedState()],
+  getters: getters
 })

+ 6 - 6
src/views/exam/ExamDetails.vue

@@ -1,6 +1,6 @@
 <template>
   <v-container>
-    <page-header title="考试详情" :breadcrumb="breadcrumb">
+    <page-header title="评测详情" :breadcrumb="breadcrumb">
       <template #extra>
         <v-row justify="end">
           <v-spacer></v-spacer>
@@ -18,7 +18,7 @@
               :to="{ name: 'exam-pane' }"
               style="text-decoration: none;"
             >
-              <v-btn color="success">进入考试</v-btn>
+              <v-btn color="success">进入评测</v-btn>
             </router-link>
           </v-col>
           <v-col cols="2">
@@ -31,15 +31,15 @@
     </page-header>
     <v-card class="mb-5" hover style="cursor: default" v-if="examInfo">
       <v-card-title class="text-h4">{{ examInfo.examName }}</v-card-title>
-      <v-card-subtitle class="text-h5 black--text">考试信息</v-card-subtitle>
+      <v-card-subtitle class="text-h5 black--text">评测信息</v-card-subtitle>
       <v-card-text class="text-body-1 black--text"
         >开始时间: {{ examInfo.startTime }} 结束时间:
-        {{ examInfo.endTime }} 考试时长:{{ examLength }}
+        {{ examInfo.endTime }} 评测时长:{{ examLength }}
       </v-card-text>
 
-      <v-card-subtitle class="text-h5 black--text">考试规则</v-card-subtitle>
+      <v-card-subtitle class="text-h5 black--text">评测规则</v-card-subtitle>
       <v-card-text>{{ examInfo.comment }}</v-card-text>
-      <v-card-subtitle class="text-h5 black--text">考试内容</v-card-subtitle>
+      <v-card-subtitle class="text-h5 black--text">评测内容</v-card-subtitle>
       <v-card-text class="text-body-1 black--text">
         客观题 {{ Object.keys(examQuestions.questionAndScore).length }} 道
         编程题 {{}} 道

+ 13 - 11
src/views/exam/ExamPane.vue

@@ -40,17 +40,18 @@
             <div class="d-flex justify-center">
               <h2>客观题</h2>
             </div>
-            <div class="d-flex justify-space-around">
-              <v-chip label>
-                {{ currentQuestionIndex + 1 }} / {{ totalQuestionNum }}</v-chip
-              >
-            </div>
             <v-progress-linear
               :value="(currentQuestionIndex / totalQuestionNum) * 100"
               :buffer-value="
                 ((currentQuestionIndex + 1) / totalQuestionNum) * 100
               "
+              class="my-3"
             ></v-progress-linear>
+            <div class="d-flex justify-space-around">
+              <v-chip label>
+                {{ currentQuestionIndex + 1 }} / {{ totalQuestionNum }}</v-chip
+              >
+            </div>
           </div>
           <div class="justify-space-between">
             <div class="d-flex justify-center">
@@ -65,11 +66,11 @@
           <div class="pa-2 px-6 text--secondary justify-space-between d-flex">
             <v-dialog v-model="exitDialog" persistent max-width="290">
               <template v-slot:activator="{ on, attrs }">
-                <v-btn color="error" v-bind="attrs" v-on="on">退出考试</v-btn>
+                <v-btn color="error" v-bind="attrs" v-on="on">退出评测</v-btn>
               </template>
               <v-card>
                 <v-card-title class="headline">
-                  确认离开考试
+                  确认离开评测
                 </v-card-title>
                 <v-card-text>退出后将无法再次进入</v-card-text>
                 <v-card-actions>
@@ -147,9 +148,9 @@
               </template>
             </choice-pane>
           </div>
-
+          <v-spacer></v-spacer>
           <div
-            v-if="!fetchingStem"
+            v-if="currentQuestionIndex >= 0"
             class="d-flex justify-space-around px-10 py-10"
           >
             <v-btn color="info" @click="nextQuestion">下一题</v-btn>
@@ -230,7 +231,8 @@ export default Vue.extend({
       // codeIndex: -1
 
       submitting: false,
-      finished: false
+      finished: false,
+      answeredx: false
     }
   },
   computed: {
@@ -344,7 +346,7 @@ export default Vue.extend({
   display: flex;
   flex-direction: column;
   justify-content: space-between;
-  margin: 5vh 10vw;
+  margin: 4vh 10vw;
   min-height: 90vh;
   padding: 10px;
 }

+ 1 - 1
src/views/exam/PersonalResult.vue

@@ -43,7 +43,7 @@ export default Vue.extend({
         {
           text: '主页',
           disabled: false,
-          href: '/student/home'
+          href: '/student'
         },
         {
           text: '评测列表',

+ 1 - 1
src/views/student/Overview.vue

@@ -54,7 +54,7 @@
       </v-btn>
     </v-dialog>
     <v-snackbar v-model="joinSuccess">
-      加入考试成功
+      加入评测成功
     </v-snackbar>
     <v-snackbar v-model="joinFailed">
       邀请码有误

+ 6 - 4
src/views/student/StudentExamList.vue

@@ -1,6 +1,6 @@
 <template>
   <v-container>
-    <page-header title="我的评测" :breadcrumb="breadcrumb">
+    <page-header :title="title" :breadcrumb="breadcrumb">
       <template #extra>
         <v-row class=" my-0 py-0" no-gutters justify="end">
           <v-spacer></v-spacer>
@@ -64,6 +64,7 @@ export default Vue.extend({
       selected: '所有评测',
       selectItems: ['所有评测', '未开始', '进行中', '已结束'],
       dialog: false,
+      title: '我的评测',
       breadcrumb: [
         {
           text: '主页',
@@ -71,12 +72,13 @@ export default Vue.extend({
           href: '/student'
         },
         {
-          text: '考试列表',
+          text: '评测列表',
           disabled: true,
-          href: '/'
+          href: '/student/exams'
         }
       ],
-      examList: [] as Array<ExamSerializer>
+      examList: [] as Array<ExamSerializer>,
+      windowSwitch: 0
     }
   },
   methods: {

+ 26 - 8
src/views/student/components/StudentExamInfoCard.vue

@@ -13,7 +13,7 @@
           >开始评测</v-btn
         >
         <v-btn
-          v-if="examInfo.state === '已结束'"
+          v-if="state === '已结束'"
           color="secondary"
           class="mx-2"
           @click="onOpenResult"
@@ -27,26 +27,34 @@
     </v-row>
     <v-row justify="space-between" dense>
       <v-col>
-        <v-card-text class="pa-0 pb-5">ID: {{ examInfo.id }}</v-card-text>
+        <v-card-text class="pa-0 pb-5"
+          >开始时间: {{ examInfo.startTime }}</v-card-text
+        >
       </v-col>
       <v-col>
         <v-card-text class="pa-0 pb-5"
-          >邀请码: {{ examInfo.inviteCode }}</v-card-text
+          >结束时间: {{ examInfo.endTime }}</v-card-text
         >
       </v-col>
       <v-col>
-        <v-card-text class="pa-0 pb-5">评测时长: {{}} 小时</v-card-text>
+        <v-card-text class="pa-0 pb-5"
+          >评测时长:
+          {{ getExamLength(examInfo.startTime, examInfo.endTime) }}
+        </v-card-text>
       </v-col>
     </v-row>
     <v-row no-gutters>
       <v-col>
         <v-card-text class="pa-0 pb-5"
-          >开始时间: {{ examInfo.startTime }}</v-card-text
+          >职位: {{ examInfo.position }}</v-card-text
         >
       </v-col>
       <v-col>
         <v-card-text class="pa-0 pb-5"
-          >结束时间: {{ examInfo.endTime }}</v-card-text
+          >技能点:
+          <span v-for="skill in examInfo.skills" :key="skill"
+            >{{ skill }}
+          </span></v-card-text
         >
       </v-col>
       <v-spacer></v-spacer>
@@ -56,10 +64,17 @@
 
 <script lang="ts">
 import Vue, { PropType } from 'vue'
-import { ExamSerializer, ExamState, ExamStateText } from '@/api/types'
+import {
+  ExamSerializer,
+  ExamState,
+  ExamStateText,
+  ID,
+  LocalDateTime
+} from '@/api/types'
+import { timeLength } from '@/utils/common'
 
 export default Vue.extend({
-  name: 'ExamInfoCard',
+  name: 'StudentExamInfoCard',
   data() {
     return {
       state: (null as unknown) as ExamStateText
@@ -75,6 +90,9 @@ export default Vue.extend({
     this.state = this.computeExamState()
   },
   methods: {
+    getExamLength(startTime: LocalDateTime, endTime: LocalDateTime): string {
+      return timeLength(startTime, endTime)
+    },
     computeExamState(): ExamStateText {
       const now = new Date().getTime()
       const startTime = new Date(this.examInfo.startTime).getTime()

+ 48 - 0
src/views/teacher/EditExam.vue

@@ -0,0 +1,48 @@
+<template>
+  <v-container>
+    <v-container>
+      <v-card class="px-3 mb-5" v-if="examInfo">
+        <v-card-title>评测「{{ examInfo.examName }}」创建成功</v-card-title>
+        <v-card-subtitle>评测ID: {{ examInfo.examId }}</v-card-subtitle>
+        <v-row no-gutters align-content="start">
+          <v-col>
+            <v-card-text class="pa-0 pb-3"
+              >评测邀请码:{{ examInfo.inviteCode }}</v-card-text
+            >
+          </v-col>
+          <v-col>
+            <v-card-text class="pa-0 pb-5"
+              >当前难度值:{{ examInfo.difficulty }}</v-card-text
+            >
+          </v-col>
+        </v-row>
+      </v-card>
+      <v-container class="grey lighten-5 pa-6">
+        <v-card-title>样卷</v-card-title>
+      </v-container>
+    </v-container>
+  </v-container>
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+import { ExamSerializer } from '@/api/types'
+import { getExamById } from '@/api/exam'
+
+export default Vue.extend({
+  name: 'EditExam',
+  data() {
+    return {
+      examInfo: (null as unknown) as ExamSerializer
+    }
+  },
+  mounted() {
+    const examId = this.$route.params.examId
+    getExamById(examId).then(({ data }) => {
+      this.examInfo = data.result
+    })
+  }
+})
+</script>
+
+<style scoped></style>

+ 116 - 129
src/views/teacher/ExamCreate.vue

@@ -1,23 +1,22 @@
 <template>
   <v-container>
-    <page-header title="创建考试" :breadcrumb="breadcrumb"> </page-header>
-    <!--创建的表格-->
-    <v-container class="grey lighten-5 pa-6" v-if="!isExamCreated">
-      <v-form ref="form" lazy-validation>
+    <page-header title="创建评测" :breadcrumb="breadcrumb"> </page-header>
+    <v-container class="grey lighten-5 pa-6">
+      <v-form ref="form" v-model="valid">
         <v-text-field
           v-model="examName"
-          :counter="10"
-          label="考试名称"
-          required
+          :counter="20"
+          label="评测名称"
           class="ma-4"
+          :rules="nameRules"
         ></v-text-field>
 
         <v-text-field
           v-model="position"
-          :counter="10"
-          label="招聘职位"
-          required
+          :counter="20"
+          label="职位"
           class="ma-4"
+          :rules="nameRules"
         ></v-text-field>
 
         <v-select
@@ -28,6 +27,8 @@
           multiple
           outlined
           class="ma-4"
+          deletable-chips
+          :rules="arrayRules"
         ></v-select>
 
         <v-subheader class="ma-2 sm">
@@ -85,6 +86,7 @@
                 readonly
                 v-bind="attrs"
                 v-on="on"
+                :rules="arrayRules"
               ></v-text-field>
             </template>
             <v-time-picker
@@ -95,124 +97,68 @@
             ></v-time-picker>
           </v-menu>
         </v-row>
-
-        <!--结束日期/时间-->
+        <v-slider
+          label="考试时长"
+          :tick-labels="examLengthTicks"
+          class="ma-4"
+          max="5"
+          v-model="examLength"
+        ></v-slider>
         <v-row class="ma-4">
-          <v-menu
-            v-model="menu3"
-            :close-on-content-click="false"
-            transition="scale-transition"
-            offset-y
-            max-width="290px"
-            min-width="290px"
-          >
-            <template v-slot:activator="{ on, attrs }">
-              <v-text-field
-                v-model="endDateFormatted"
-                label="结束日期"
-                prepend-icon="mdi-calendar"
-                readonly
-                v-bind="attrs"
-                v-on="on"
-              ></v-text-field>
-            </template>
-            <v-date-picker
-              v-model="endDate"
-              no-title
-              @input="menu3 = false"
-            ></v-date-picker>
-          </v-menu>
-
-          <v-menu
-            v-model="menu4"
-            ref="menu4"
-            :close-on-content-click="false"
-            :nudge-right="40"
-            :return-value.sync="endTime"
-            transition="scale-transition"
-            offset-y
-            max-width="290px"
-            min-width="290px"
-          >
-            <template v-slot:activator="{ on, attrs }">
-              <v-text-field
-                v-model="endTime"
-                label="结束时间"
-                prepend-icon="mdi-clock-time-four-outline"
-                readonly
-                v-bind="attrs"
-                v-on="on"
-              ></v-text-field>
-            </template>
-            <v-time-picker
-              v-if="menu4"
-              v-model="endTime"
-              full-width
-              @click:minute="$refs.menu4.save(endTime)"
-            ></v-time-picker>
-          </v-menu>
+          <v-text-field
+            :value="computedEndDate"
+            label="结束日期"
+            prepend-icon="mdi-calendar"
+            disabled
+          ></v-text-field>
+          <v-text-field
+            :value="computedEndTime"
+            label="结束时间"
+            prepend-icon="mdi-clock-time-four-outline"
+            disabled
+          ></v-text-field>
         </v-row>
-
+        <v-switch
+          class="ma-4"
+          v-model="antiCheating"
+          :label="`在线监考: ${antiCheating ? '开' : '关'}`"
+        ></v-switch>
         <v-text-field
           v-model="comment"
           label="备注"
           class="ma-4 mb-8"
         ></v-text-field>
-        <v-btn color="success" class="ma-4" @click="onCreateExam">
-          提交创建
-        </v-btn>
-        <v-btn color="primary" class="ma-4" @click="selectQuestion = true">
-          手动选择试题
+        <v-btn
+          color="success"
+          class="ma-4"
+          @click="onCreateExam"
+          :disabled="!valid"
+        >
+          创建评测
         </v-btn>
+        <!--        <v-btn color="primary" class="ma-4" @click="selectQuestion = true">-->
+        <!--          手动选择试题-->
+        <!--        </v-btn>-->
       </v-form>
     </v-container>
-
-    <!--创建成功后显示-->
-    <v-container v-else>
-      <v-card class="px-3 mb-5">
-        <v-card-title
-          >考试「{{ createdExamInfo.examName }}」创建成功</v-card-title
-        >
-        <v-card-subtitle>考试ID: {{ createdExamInfo.examId }}</v-card-subtitle>
-        <v-row no-gutters align-content="start">
-          <v-col>
-            <v-card-text class="pa-0 pb-3"
-              >考试邀请码:{{ createdExamInfo.inviteCode }}</v-card-text
-            >
-          </v-col>
-          <v-col>
-            <v-card-text class="pa-0 pb-5"
-              >当前难度值:{{ createdExamInfo.difficulty }}</v-card-text
-            >
-          </v-col>
-        </v-row>
-      </v-card>
-
-      <v-container class="grey lighten-5 pa-6">
-        <v-card-title>样卷</v-card-title>
-      </v-container>
-    </v-container>
-
-    <v-container v-if="selectQuestion">
-      <v-overlay :value="true" :dark="false" absolute>
-        <question-list></question-list>
-      </v-overlay>
-    </v-container>
+    <v-snackbar>
+      创建成功
+    </v-snackbar>
   </v-container>
 </template>
 
 <script lang="ts">
 import Vue from 'vue'
 import PageHeader from '@/components/PageHeader.vue'
-import QuestionList from '@/views/teacher/components/QuestionList.vue'
 import { createExam } from '@/api/exam'
 import { ExamSerializer, ID } from '@/api/types'
+import { mapGetters } from 'vuex'
+import dayjs from 'dayjs'
 
 export default Vue.extend({
   name: 'ExamCreate',
   components: {
-    PageHeader,
-    QuestionList
+    PageHeader
   },
 
   data() {
@@ -224,7 +170,7 @@ export default Vue.extend({
           href: '/'
         },
         {
-          text: '创建考试',
+          text: '创建评测',
           disabled: true,
           href: '/'
         }
@@ -241,7 +187,7 @@ export default Vue.extend({
         'Vue'
       ],
       isExamCreated: false,
-      createdExamInfo: {} as ExamSerializer,
+      createdExamInfo: (null as unknown) as ExamSerializer,
       menu1: false,
       menu2: false,
       menu3: false,
@@ -249,7 +195,7 @@ export default Vue.extend({
       examName: '',
       position: '',
       skills: [],
-      difficulty: 1,
+      difficulty: 50,
       startDate: '',
       startDateFormatted: '',
       startTime: '',
@@ -259,28 +205,62 @@ export default Vue.extend({
       comment: '',
       selectedQuestions: [] as Array<ID>,
       selectQuestion: false,
-      antiCheating: false
+      antiCheating: false,
+      valid: true,
+      nameRules: [
+        (v: string) => !!v || '该项为必填项',
+        (v: string) => v.length < 20 || '字数过多'
+      ],
+      arrayRules: [(v: [] | string) => v.length > 0 || '该项为必填项'],
+      examLengthTicks: [
+        '0.5 小时',
+        '1 小时',
+        '1.5 小时',
+        '2 小时',
+        '2.5 小时',
+        '3 小时'
+      ],
+      examLength: 0,
+      timeFormat: 'YYYY-MM-DD HH:mm:ss'
     }
   },
   computed: {
-    computedStartDateFormatted(): string {
-      return this.formatDate(this.startDate)
+    computedEndDate(): string {
+      if (this.startTime === '') {
+        return this.startDateFormatted
+      }
+      return this.computedExamEndTime.substr(0, 10)
+    },
+    computedEndTime(): string {
+      if (this.startTime === '') {
+        return ''
+      }
+
+      return this.computedExamEndTime.substr(11, 5)
     },
-    computedEndDateFormatted(): string {
-      return this.formatDate(this.endDate)
+    computedExamEndTime(): string {
+      const startTime = dayjs(
+        this.startDateFormatted + ' ' + this.startTime + ':00',
+        this.timeFormat
+      )
+
+      const examLength = Number.parseFloat(
+        this.examLengthTicks[this.examLength]
+      )
+      const endTime = startTime.add(examLength, 'h')
+
+      return endTime.format(this.timeFormat)
     }
   },
 
   watch: {
     startDate(val) {
       this.startDateFormatted = this.formatDate(this.startDate)
-    },
-    endDate(val) {
-      this.endDateFormatted = this.formatDate(this.endDate)
     }
   },
 
   methods: {
+    ...mapGetters(['getId']),
     saveSelectedQuestions(questions: Array<ID>) {
       console.log(questions)
       this.selectedQuestions = questions
@@ -298,32 +278,39 @@ export default Vue.extend({
       return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
     },
     onCreateExam() {
+      // if (!this.$refs.form.validate()) {
+      //   return
+      // }
+      console.log(this.valid)
+
       const obj = {
-        creatorId: 'userId1',
+        creatorId: this.getId(),
         examName: this.examName,
         position: this.position,
         skills: this.skills,
         difficulty: this.difficulty,
         startTime: this.startDateFormatted + ' ' + this.startTime + ':00',
-        endTime: this.endDateFormatted + ' ' + this.endTime + ':00',
+        endTime: this.computedExamEndTime,
         comment: this.comment,
         antiCheating: this.antiCheating
       } as ExamSerializer
-      console.log('创建考试', obj)
+      console.log('创建评测', obj)
       createExam(obj).then(({ data }) => {
-        console.log(data.result)
-        this.isExamCreated = data.result ? true : false
-        this.createdExamInfo = obj
+        this.isExamCreated = !!data.result
+        const examId = data.result.examId
+        this.$router.push({
+          name: 'edit-exams',
+          params: {
+            examId: examId.toString()
+          }
+        })
       })
     }
   },
   mounted() {
-    this.startDateFormatted = this.formatDate(
-      new Date().toISOString().substr(0, 10)
-    )
-    this.endDateFormatted = this.formatDate(
-      new Date().toISOString().substr(0, 10)
-    )
+    this.startDateFormatted = dayjs()
+      .format(this.timeFormat)
+      .substr(0, 10)
   }
 })
 </script>

+ 15 - 6
src/views/teacher/ExamManage.vue

@@ -23,7 +23,7 @@
           </v-col>
 
           <v-col cols="1" class="ml-5">
-            <v-btn color="secondary" fab small>
+            <v-btn color="secondary" fab small @click="refreshState">
               <v-icon dark>mdi-cached</v-icon>
             </v-btn>
           </v-col>
@@ -39,7 +39,7 @@
     </teacher-exam-info-card>
     <v-pagination
       v-model="page"
-      :length="totalNum / pageSize"
+      :length="Math.floor(totalNum / pageSize) + 1"
       @input="changePage"
     >
     </v-pagination>
@@ -65,8 +65,8 @@ export default Vue.extend({
       page: 1,
       totalNum: 0,
       pageSize: 5,
-      selected: 0 as ExamState,
-      selectItems: ['所有考试', '未开始', '进行中', '已结束'],
+      selected: '所有评测',
+      selectItems: ['所有评测', '未开始', '进行中', '已结束'],
       dialog: false,
 
       breadcrumb: [
@@ -76,7 +76,7 @@ export default Vue.extend({
           href: '/student'
         },
         {
-          text: '考试列表',
+          text: '评测列表',
           disabled: true,
           href: '/'
         }
@@ -89,12 +89,21 @@ export default Vue.extend({
     changePage() {
       this.fetchExams()
     },
+    refreshState() {
+      this.page = 1
+      this.fetchExams(1)
+    },
     onCreateExam() {
       // this.$router.push({ name: 'exam-create' })
       this.$router.push('/teacher/exam-create')
     },
     fetchExams(pageNum: number = 1) {
-      getExamsByTeacherId({ pageNum: pageNum, pageSize: 5 }, this.selected)
+      getExamsByTeacherId(
+        { pageNum: pageNum, pageSize: 5 },
+        this.selectItems.findIndex((val) => {
+          return this.selected === val
+        }) as ExamState
+      )
         .then(({ data }) => {
           this.examList = data.result.examVOList
           this.totalNum = data.result.totalNum

+ 25 - 12
src/views/teacher/components/TeacherExamInfoCard.vue

@@ -9,7 +9,7 @@
       </v-col>
       <v-col class="mr-3 d-flex justify-end">
         <v-btn
-          v-if="examInfo.status === 'FINISHED'"
+          v-if="examInfo.status === '已结束'"
           color="secondary"
           class="mx-2"
         >
@@ -18,11 +18,11 @@
         <v-btn
           color="warning"
           class="mx-2"
-          v-if="examInfo.status === 'AVAILABLE'"
+          v-if="examInfo.status === '进行中'"
           @click="onExtendTime"
           >延长时间</v-btn
         >
-        <v-btn color="info" class="mx-2" v-if="examInfo.status === 'READY'"
+        <v-btn color="info" class="mx-2" v-if="examInfo.status === '未开始'"
           >修改评测</v-btn
         >
         <v-btn color="primary" class="mx-2" @click="showExamDetail"
@@ -31,9 +31,6 @@
       </v-col>
     </v-row>
     <v-row justify="space-between" dense>
-      <v-col>
-        <v-card-text class="pa-0 pb-5">ID: {{ examInfo.id }}</v-card-text>
-      </v-col>
       <v-col>
         <v-card-text class="pa-0 pb-5"
           >邀请码: {{ examInfo.inviteCode }}</v-card-text
@@ -41,8 +38,9 @@
       </v-col>
       <v-col>
         <v-card-text class="pa-0 pb-5"
-          >评测时长: {{ examInfo.lastTime }} 小时</v-card-text
-        >
+          >评测时长:
+          {{ getExamLength(examInfo.startTime, examInfo.endTime) }}
+        </v-card-text>
       </v-col>
     </v-row>
     <v-row no-gutters>
@@ -57,8 +55,8 @@
         >
       </v-col>
       <v-col>
-        <v-card-text class="pa-0 pb-5"
-          >参加人数: {{ examInfo.participantsNumber }}</v-card-text
+        <v-card-text class="pa-0 pb-5" v-if="joinedUsers.length > 0"
+          >参加人数: {{ joinedUsers.length }}</v-card-text
         >
       </v-col>
     </v-row>
@@ -67,12 +65,16 @@
 
 <script lang="ts">
 import Vue, { PropType } from 'vue'
-import { ExamSerializer } from '@/api/types'
+import { ExamSerializer, LocalDateTime, UserSerializer } from '@/api/types'
+import { timeLength } from '@/utils/common'
+import { getExamJoinedUsers } from '@/api/user'
 
 export default Vue.extend({
   name: 'ExamInfoCard',
   data() {
-    return {}
+    return {
+      joinedUsers: [] as Array<UserSerializer>
+    }
   },
   props: {
     examInfo: {
@@ -81,6 +83,9 @@ export default Vue.extend({
     }
   },
   methods: {
+    getExamLength(startTime: LocalDateTime, endTime: LocalDateTime): string {
+      return timeLength(startTime, endTime)
+    },
     onExtendTime() {},
     showExamDetail() {
       const examId = this.examInfo.examId?.toString()
@@ -93,6 +98,14 @@ export default Vue.extend({
         })
       }
     }
+  },
+  mounted() {
+    if (this.examInfo.examId) {
+      getExamJoinedUsers(this.examInfo.examId).then(({ data }) => {
+        this.joinedUsers = data.result
+        console.log(this.joinedUsers.length)
+      })
+    }
   }
 })
 </script>

+ 5 - 0
yarn.lock

@@ -3183,6 +3183,11 @@ date-fns@^1.27.2:
   resolved "https://registry.npm.taobao.org/date-fns/download/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
   integrity sha1-LnG/CxGRU9u0zE6I2epaz7UNwFw=
 
+dayjs@^1.10.4:
+  version "1.10.4"
+  resolved "https://registry.npm.taobao.org/dayjs/download/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2"
+  integrity sha1-jlRKm4aD9heD9XCYCoqA6vVKseI=
+
 de-indent@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npm.taobao.org/de-indent/download/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"