ChengYuXuan 5 лет назад
Родитель
Сommit
3e7b4b9564

+ 2 - 0
src/api/types.d.ts

@@ -41,6 +41,7 @@ export interface ExamSerializer {
   skills: Array<string>
   startTime: LocalDateTime
   examName: string
+  isPublic: boolean
   // 上边是创建评测所需的信息
   examId?: number
   inviteCode?: string
@@ -83,6 +84,7 @@ export type OptionSerializer = Map<string, string>
 export interface OptionLabelTextPair {
   label: string
   text: string
+  labelText?: string
 }
 
 export interface QuestionStemSerializer {

+ 5 - 3
src/mock/serializers.js

@@ -2,7 +2,9 @@ const { randomArrayValue } = require('./utils')
 
 const basicExtendsTypes = () => ({
   // LOCAL_DATE_TIME: () => Math.floor(new Date().getTime() / 1000),
-  USER_ROLE: (role) => role || randomArrayValue(['STUDENT', 'HR']),
+  USER_ROLE: (role) => role || randomArrayValue(['STUDENT', 'TEACHER']),
+  START_TIME: () => randomArrayValue(['2021-02-01 00:00:00']),
+  END_TIME: () => randomArrayValue(['2021-03-01 00:00:00']),
   BUILD_STATUS: (status) =>
     status ||
     randomArrayValue([
@@ -38,11 +40,11 @@ const serializersPlugin = (mock) => ({
       comment: '@CPARAGRAPH',
       'creatorId|100-1000': 1,
       'difficulty|10-100': 1,
-      endTime: '@DATETIME',
+      endTime: '@END_TIME',
       'examId|1-2000': 1,
       position: '@CTITLE',
       'skills|1-3': ['@CWORD'],
-      startTime: '@DATETIME',
+      startTime: '@START_TIME',
       examName: '@CTITLE',
       inviteCode: '@WORD(5)'
     }),

+ 2 - 2
src/views/exam/BeforeExam.vue

@@ -13,8 +13,8 @@
             </router-link>
           </v-col>
           <v-col cols="2">
-            <router-link to="/student/exams">
-              <v-btn color="error">返回</v-btn>
+            <router-link to="/student/exams" style="text-decoration: none">
+              <v-btn>返回</v-btn>
             </router-link>
           </v-col>
         </v-row>

+ 12 - 4
src/views/exam/ExamPane.vue

@@ -183,8 +183,11 @@
       <v-snackbar v-model="finished" absolute>
         已到最后一题
       </v-snackbar>
-      <v-snackbar v-model="submitting" absolute centered>
-        <span v-if="examTimesUp">考试结束</span> 提交试卷中...
+      <v-snackbar v-model="submitting" absolute centered color="info">
+        <span v-if="examTimesUp">评测结束</span> 提交试卷中...
+      </v-snackbar>
+      <v-snackbar v-model="examAvailableDialog" centered absolute color="error">
+        评测不可用
       </v-snackbar>
     </v-main>
   </div>
@@ -236,7 +239,7 @@ export default Vue.extend({
       examId: 0,
       // codeQuestions: [] as Array<CodeQuestionSerializer>,
       // codeIndex: -1
-
+      examAvailableDialog: false,
       submitting: false,
       finished: false,
       answered: false,
@@ -347,8 +350,13 @@ export default Vue.extend({
         this.examInfo = data.result
         this.endTime = dayjs(this.examInfo.endTime, TIME_FORMAT).valueOf()
         this.currentTime = dayjs().valueOf()
+        const startTime = dayjs(this.examInfo.startTime, TIME_FORMAT).valueOf()
 
-        if (this.currentTime >= this.endTime) {
+        if (this.currentTime >= this.endTime || this.currentTime < startTime) {
+          this.examAvailableDialog = true
+          setTimeout(() => {
+            this.$router.push({ name: 'Home' })
+          }, 1000)
         }
       })
 

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

@@ -4,8 +4,8 @@
       <template #extra>
         <v-row justify="end">
           <v-col cols="2">
-            <router-link to="/student/exams">
-              <v-btn color="error">返回</v-btn>
+            <router-link to="/student/exams" style="text-decoration: none">
+              <v-btn>返回</v-btn>
             </router-link>
           </v-col>
         </v-row>

+ 18 - 21
src/views/exam/components/ChoicePane.vue

@@ -29,10 +29,11 @@
                 >
                 </v-checkbox>
               </v-list-item-action>
-              <v-list-item-content>
-                <v-list-item-title class="text-wrap"
-                  >{{ choice.label }}: {{ choice.text }}</v-list-item-title
-                >
+              <v-list-item-content class="py-0">
+                <markdown-text
+                  class="mt-4"
+                  :raw="choice.labelText"
+                ></markdown-text>
               </v-list-item-content>
             </template>
           </v-list-item>
@@ -47,10 +48,11 @@
               <v-list-item-action>
                 <v-checkbox hide-details :input-value="active"> </v-checkbox>
               </v-list-item-action>
-              <v-list-item-content>
-                <v-list-item-title class="text-wrap"
-                  >{{ choice.label }}: {{ choice.text }}</v-list-item-title
-                >
+              <v-list-item-content class="py-0">
+                <markdown-text
+                  class="mt-4"
+                  :raw="choice.labelText"
+                ></markdown-text>
               </v-list-item-content>
             </template>
           </v-list-item>
@@ -64,11 +66,8 @@
 <script lang="ts">
 import Vue, { PropType } from 'vue'
 import MarkdownText from '@/views/exam/components/MarkdownText.vue'
-import {
-  QuestionStemSerializer,
-  OptionSerializer,
-  OptionLabelTextPair
-} from '@/api/types'
+import { QuestionStemSerializer, OptionLabelTextPair } from '@/api/types'
+
 import { clone } from 'ramda'
 export default Vue.extend({
   name: 'ChoicePane',
@@ -118,7 +117,11 @@ export default Vue.extend({
       this.options = []
 
       for (let i = 0; i < keys.length; i++) {
-        this.options.push({ label: keys[i], text: values[i] })
+        this.options.push({
+          label: keys[i],
+          text: values[i],
+          labelText: keys[i] + ' : ' + values[i]
+        })
       }
       this.answer = []
       this.loading = false
@@ -130,10 +133,4 @@ export default Vue.extend({
 })
 </script>
 
-<style scoped>
-.questionCard {
-  margin: 5vh 10vw;
-  min-height: 90vh;
-  padding: 10px;
-}
-</style>
+<style scoped></style>

+ 2 - 5
src/views/exam/components/CountDown.vue

@@ -1,7 +1,7 @@
 <template>
   <span>
     <v-chip label> 离结束还有:{{ content }} </v-chip>
-    <v-snackbar v-model="endTimeChanged">考试已延长</v-snackbar>
+    <v-snackbar v-model="endTimeChanged">评测已延长</v-snackbar>
   </span>
 </template>
 <script>
@@ -59,10 +59,7 @@ export default {
 
         if (t > 0) {
           const duration = dayjs.duration(t, 'second')
-          if (
-            duration.get('minute') % 5 === 0 &&
-            duration.get('second') === 0
-          ) {
+          if (duration.get('second') === 0) {
             getExamById(examId)
               .then(({ data }) => {
                 const newEndTime = data.result.endTime

+ 6 - 2
src/views/exam/components/MarkdownText.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-html="markdownToHTML(raw)" class="pa-2 markdown-content"></div>
+  <div v-html="markdownToHTML(raw)" class="markdown-content"></div>
 </template>
 
 <script lang="ts">
@@ -17,9 +17,13 @@ export default Vue.extend({
 })
 </script>
 
-<style>
+<style lang="scss" scoped>
 .markdown-content {
   word-break: break-all;
   white-space: pre-wrap;
+
+  p {
+    margin-bottom: 0;
+  }
 }
 </style>

+ 1 - 3
src/views/student/StudentExamList.vue

@@ -101,9 +101,7 @@ export default Vue.extend({
 
       getExamsByStudentId(
         { pageNum: this.page, pageSize: this.pageSize },
-        this.selectItems.findIndex((val) => {
-          return this.selected === val
-        }) as ExamState
+        this.selectItems.indexOf(this.selected) as ExamState
       )
         .then(({ data }) => {
           this.examList = data.result.examVOList

+ 59 - 10
src/views/student/components/StudentExamInfoCard.vue

@@ -2,7 +2,22 @@
   <v-card hover class="ma-5 px-3 my-8" style="cursor: default;height: 20%">
     <v-row>
       <v-col class="d-flex">
-        <v-card-title class="ma-0 pa-0">{{ examInfo.examName }}</v-card-title>
+        <v-card-title class="ma-0 pa-0">
+          <v-tooltip top v-if="examInfo.isPublic">
+            <template v-slot:activator="{ on, attrs }">
+              <v-icon v-bind="attrs" v-on="on">mdi-lock-off</v-icon>
+            </template>
+            <span>无需邀请码即可加入</span>
+          </v-tooltip>
+          <v-tooltip top v-else>
+            <template v-slot:activator="{ on, attrs }">
+              <v-icon v-bind="attrs" v-on="on">mdi-lock</v-icon>
+            </template>
+            <span>输入邀请码加入评测</span>
+          </v-tooltip>
+
+          {{ examInfo.examName }}</v-card-title
+        >
         <v-chip :color="statusToColor(state)" label class="ml-5" outlined>{{
           state
         }}</v-chip>
@@ -71,6 +86,20 @@
       </v-col>
       <v-spacer></v-spacer>
     </v-row>
+    <v-dialog v-model="inputInviteCode" max-width="500">
+      <v-card class="pa-2 pt-3">
+        <v-text-field label="请输入邀请码" v-model="inviteCode"> </v-text-field>
+        <v-card-actions class="justify-end">
+          <v-btn color="success" @click="joinExamByInviteCode">确定</v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+    <v-snackbar top v-model="joinSuccess" timeout="2000" color="success">
+      加入评测成功
+    </v-snackbar>
+    <v-snackbar top v-model="joinFailed" timeout="2000" color="error">
+      加入评测失败,验证码错误
+    </v-snackbar>
   </v-card>
 </template>
 
@@ -84,7 +113,11 @@ export default Vue.extend({
   name: 'StudentExamInfoCard',
   data() {
     return {
-      state: (null as unknown) as ExamStateText
+      state: (null as unknown) as ExamStateText,
+      inviteCode: '',
+      inputInviteCode: false,
+      joinSuccess: false,
+      joinFailed: false
     }
   },
   props: {
@@ -103,15 +136,31 @@ export default Vue.extend({
     getExamLength(startTime: LocalDateTime, endTime: LocalDateTime): string {
       return timeLength(startTime, endTime)
     },
-    onJoinExam() {
+    joinExamByInviteCode() {
       const examId = this.examInfo.examId ?? 0
-      const inviteCode = this.examInfo.inviteCode ?? ''
-      joinExamByInviteCode(inviteCode, examId).then(({ data }) => {
-        const joinSuccess = data.result
-        if (joinSuccess) {
-          this.$emit('joinExam')
-        }
-      })
+      const inviteCode = this.inviteCode
+      joinExamByInviteCode(inviteCode, examId)
+        .then(({ data }) => {
+          const joinSuccess = data.result
+          this.inputInviteCode = false
+          if (joinSuccess) {
+            this.joinSuccess = true
+            this.$emit('joinExam')
+          } else {
+            this.joinFailed = true
+          }
+        })
+        .finally(() => {
+          this.inviteCode = ''
+        })
+    },
+
+    onJoinExam() {
+      if (this.examInfo.isPublic) {
+        //TODO
+      } else {
+        this.inputInviteCode = true
+      }
     },
     onOpenResult() {
       const examId = this.examInfo.examId?.toString() ?? ''

+ 15 - 2
src/views/teacher/EditExam.vue

@@ -1,6 +1,19 @@
 <template>
   <v-container>
-    <page-header title="评测设置" :breadcrumb="breadcrumb"></page-header>
+    <page-header title="评测设置" :breadcrumb="breadcrumb">
+      <template #extra>
+        <v-row justify="end">
+          <v-col cols="2">
+            <router-link
+              to="/teacher/created-exams"
+              style="text-decoration: none"
+            >
+              <v-btn>返回</v-btn>
+            </router-link>
+          </v-col>
+        </v-row>
+      </template>
+    </page-header>
     <v-container>
       <v-card class="px-3 mb-5" v-if="examInfo">
         <v-card-title v-if="isSampleSet"
@@ -79,7 +92,7 @@
         </v-tabs-items>
       </v-container>
     </v-container>
-    <v-snackbar v-model="success">
+    <v-snackbar v-model="success" absolute>
       设置样卷成功
     </v-snackbar>
   </v-container>

+ 30 - 8
src/views/teacher/ExamCreate.vue

@@ -1,6 +1,19 @@
 <template>
   <v-container>
-    <page-header title="创建评测" :breadcrumb="breadcrumb"> </page-header>
+    <page-header title="创建评测" :breadcrumb="breadcrumb">
+      <template #extra>
+        <v-row justify="end">
+          <v-col cols="2">
+            <router-link
+              to="/teacher/created-exams"
+              style="text-decoration: none"
+            >
+              <v-btn>返回</v-btn>
+            </router-link>
+          </v-col>
+        </v-row>
+      </template>
+    </page-header>
     <v-container class="grey lighten-5 pa-6">
       <v-form ref="form" v-model="valid">
         <v-text-field
@@ -101,7 +114,7 @@
           </v-menu>
         </v-row>
         <v-slider
-          label="考试时长"
+          label="评测时长"
           :tick-labels="examLengthTicks"
           class="ma-4"
           max="5"
@@ -121,11 +134,18 @@
             disabled
           ></v-text-field>
         </v-row>
-        <v-switch
-          class="ma-4"
-          v-model="antiCheating"
-          :label="`在线监考: ${antiCheating ? '开' : '关'}`"
-        ></v-switch>
+        <v-row class="pl-2">
+          <v-switch
+            class="ma-4"
+            v-model="antiCheating"
+            :label="`在线监考: ${antiCheating ? '开' : '关'}`"
+          ></v-switch>
+          <v-switch
+            class="ma-4"
+            v-model="isPrivate"
+            :label="`需要邀请码加入: ${isPrivate ? '是' : '否'}`"
+          ></v-switch>
+        </v-row>
         <v-text-field
           v-model="comment"
           label="备注"
@@ -215,6 +235,7 @@ export default Vue.extend({
       selectedQuestions: [] as Array<ID>,
       selectQuestion: false,
       antiCheating: false,
+      isPrivate: false,
       valid: true,
       nameRules: [
         (v: string) => !!v || '该项为必填项',
@@ -302,7 +323,8 @@ export default Vue.extend({
         startTime: this.startDateFormatted + ' ' + this.startTime + ':00',
         endTime: this.computedExamEndTime,
         comment: this.comment,
-        antiCheating: this.antiCheating
+        antiCheating: this.antiCheating,
+        isPublic: !this.isPrivate
       } as ExamSerializer
       console.log('创建评测', obj)
       createExam(obj).then(({ data }) => {

+ 1 - 3
src/views/teacher/ExamManage.vue

@@ -107,9 +107,7 @@ export default Vue.extend({
       this.fetching = true
       getExamsByTeacherId(
         { pageNum: this.page, pageSize: this.pageSize },
-        this.selectItems.findIndex((val) => {
-          return this.selected === val
-        }) as ExamState
+        this.selectItems.indexOf(this.selected) as ExamState
       )
         .then(({ data }) => {
           this.examList = data.result.examVOList

+ 14 - 1
src/views/teacher/TeacherExamDetail.vue

@@ -1,6 +1,19 @@
 <template>
   <v-container>
-    <page-header title="考试信息" :breadcrumb="breadcrumb"></page-header>
+    <page-header title="评测信息" :breadcrumb="breadcrumb">
+      <template #extra>
+        <v-row justify="end">
+          <v-col cols="2">
+            <router-link
+              to="/teacher/created-exams"
+              style="text-decoration: none"
+            >
+              <v-btn>返回</v-btn>
+            </router-link>
+          </v-col>
+        </v-row>
+      </template>
+    </page-header>
     <v-container class="grey lighten-5 pa-6 d-flex flex-column">
       <div class="text-center display-1">试卷</div>
       <div v-if="!fetching">

+ 16 - 2
src/views/teacher/components/TeacherExamInfoCard.vue

@@ -2,7 +2,21 @@
   <v-card hover class="ma-5 px-3 my-8" style="cursor: default;height: 20%">
     <v-row>
       <v-col class="d-flex">
-        <v-card-title class="ma-0 pa-0">{{ examInfo.examName }}</v-card-title>
+        <v-card-title class="ma-0 pa-0">
+          <v-tooltip top v-if="examInfo.isPublic">
+            <template v-slot:activator="{ on, attrs }">
+              <v-icon v-bind="attrs" v-on="on">mdi-lock-off</v-icon>
+            </template>
+            <span>无需邀请码即可加入</span>
+          </v-tooltip>
+          <v-tooltip top v-else>
+            <template v-slot:activator="{ on, attrs }">
+              <v-icon v-bind="attrs" v-on="on">mdi-lock</v-icon>
+            </template>
+            <span>输入邀请码加入评测</span>
+          </v-tooltip>
+          {{ examInfo.examName }}</v-card-title
+        >
         <v-chip :color="statusToColor(state)" label class="ml-5" outlined>{{
           state
         }}</v-chip>
@@ -77,7 +91,7 @@
       </v-card>
     </v-dialog>
     <v-snackbar v-model="extendSuccess" centered>
-      延长考试时间成功
+      延长评测时间成功
     </v-snackbar>
   </v-card>
 </template>