소스 검색

手动调整题目

ChengYuXuan 5 년 전
부모
커밋
f02c591be6

+ 11 - 1
src/api/exam.ts

@@ -9,7 +9,8 @@ import {
   LocalDateTime,
   ExamState,
   ExamListSerializer,
-  CreateExamResponse
+  CreateExamResponse,
+  QuestionAndScore
 } from '@/api/types'
 
 export const createExam = (exam: ExamSerializer) => {
@@ -73,3 +74,12 @@ export const getExamsByTeacherId = (page: Pageable, state: ExamState) => {
     `${EXAM_MODULE}/teacher/exams?pageSize=${page.pageSize}&pageNum=${page.pageNum}&state=${state}`
   )
 }
+
+export const addExamPaperQuestions = (
+  examId: ID,
+  questions: QuestionAndScore
+) => {
+  return axios.post(`${EXAM_MODULE}/${examId}/questions/`, {
+    questionScoreMap: questions
+  })
+}

+ 27 - 1
src/api/question.ts

@@ -5,7 +5,9 @@ import {
   TeacherQuestionSerializer,
   QuestionSerializer,
   ReceivedData,
-  SampleSerializer
+  SampleSerializer,
+  Pageable,
+  QuestionListSerializer
 } from '@/api/types'
 
 export const getQuestionInfo = (id: ID) => {
@@ -25,3 +27,27 @@ export const getSamplePaper = () => {
     `${QUESTION_MODULE}/sample`
   )
 }
+
+export const getQuestionList = (
+  page: Pageable,
+  stem?: string,
+  tags?: string,
+  knowledgeName?: string
+) => {
+  let params = ''
+  if (stem) {
+    params += `stem=${stem}`
+  }
+  if (tags) {
+    params += `tags=${tags}`
+  }
+  if (knowledgeName) {
+    params += `knowledgeName=${knowledgeName}`
+  }
+  if (params) {
+    params += '&'
+  }
+  return axios.get<ReceivedData<QuestionListSerializer>>(
+    `${QUESTION_MODULE}/question?${params}pageSize=${page.pageSize}&pageNum=${page.pageNum}`
+  )
+}

+ 8 - 3
src/api/types.d.ts

@@ -23,6 +23,7 @@ export type QuestionType =
   | 'code'
   | 'multi_choice'
   | 'true_false'
+  | 'short_answer'
 export interface ErrorData extends AxiosResponseInfo {
   err: number
   msg: string
@@ -32,7 +33,7 @@ export interface PageSerializer {
   totalNum: number
 }
 
-export type Pageable<T = {}> = T & {
+export interface Pageable {
   pageNum: number
   pageSize: number
 }
@@ -97,7 +98,7 @@ export interface OptionLabelTextPair {
 }
 
 export interface QuestionSerializer {
-  id: number
+  id: ID
   stem: string
   type: QuestionType
   // 客观题
@@ -107,6 +108,10 @@ export interface QuestionSerializer {
   knowledgeId?: Array<string>
 }
 
+export type QuestionListSerializer = PageSerializer & {
+  questionStemList: Array<QuestionSerializer>
+}
+
 export interface ObjectiveQuestionSerializer extends QuestionSerializer {
   options: OptionSerializer
   keyPoints: string
@@ -157,7 +162,7 @@ export interface UserSerializer {
   role: UserRole
 }
 
-export type QuestionAndScore = Map<string, number>
+export type QuestionAndScore = Object // {ID:number}
 
 export interface Record {
   answer: string

+ 0 - 2
src/store/index.ts

@@ -13,8 +13,6 @@ export type RootState = {
 export const state = (): RootState => ({
   user: (null as unknown) as UserSerializer,
   token: ''
-  // token:
-  //   'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJwLnNlZWNvZGVyLmNuIiwic3ViIjoiMTU5OTYyMzEzMTMiLCJhdWQiOiJzZWVjLXBvcnRhbCIsImF1dGhfdGltZSI6MTYxMjAxMjk1MCwiaWF0IjoxNjEyMDEyOTUwLCJleHAiOjE2MTIwOTkzNTAsInVzZXJfaW5mbyI6eyJwaG9uZSI6IjE1OTk2MjMxMzEzIiwiZW1haWwiOiIxNjExNTAwMDlAc21haWwubmp1LmVkdS5jbiIsImlkIjozMzIsIm5hbWUiOiIxNjExNTAwMDkiLCJyb2xlIjoiU1RVREVOVCJ9fQ.qLCB7yQnH1_ZLzl5Ir_9LuDYmn6xQk7ZRlFxywMKYgc'
 })
 
 export const mutations: MutationTree<RootState> = {

+ 18 - 1
src/utils/common.ts

@@ -1,4 +1,4 @@
-import { ExamStateText, LocalDateTime } from '@/api/types'
+import { ExamStateText, LocalDateTime, QuestionType } from '@/api/types'
 import dayjs from 'dayjs'
 import Duration from 'dayjs/plugin/duration'
 dayjs.extend(Duration)
@@ -69,3 +69,20 @@ export const statusToColor = function(state: ExamStateText): string {
       return ''
   }
 }
+
+export const questionTypeToText = function(type: QuestionType): string {
+  switch (type) {
+    case 'choice':
+      return '单选题'
+    case 'multi_choice':
+      return '多选题'
+    case 'true_false':
+      return '判断题'
+    case 'blank':
+      return '填空题'
+    case 'code':
+      return '编程题'
+    case 'short_answer':
+      return '简答题'
+  }
+}

+ 1 - 2
src/views/components/ExamQuestionsInfo.vue

@@ -16,9 +16,8 @@
         </v-col>
         <v-col class="flex-shrink-1" cols="2">
           <v-card-text v-if="question.keyPoints">
-            知识点: {{ question.keyPoints }}
+            关键词: {{ question.keyPoints }}
           </v-card-text>
-          <v-card-text v-else class="text-end">编程题</v-card-text>
         </v-col>
       </v-row>
       <v-row class="px-10 d-block" v-if="analysis && question.type !== 'code'">

+ 3 - 0
src/views/exam/components/MarkdownText.vue

@@ -25,5 +25,8 @@ export default Vue.extend({
   p {
     margin-bottom: 0;
   }
+  img {
+    max-width: 100%;
+  }
 }
 </style>

+ 123 - 43
src/views/teacher/EditExam.vue

@@ -12,13 +12,13 @@
         </v-row>
       </template>
     </page-header>
-    <v-container>
+    <v-container v-show="!showQuestionList">
       <v-card class="px-3 mb-5" v-if="examInfo">
-        <v-card-title v-if="isSampleSet"
+        <v-card-title v-if="paperQuestionsSet"
           >评测「{{ examInfo.examName }}」</v-card-title
         >
         <v-card-title v-else
-          >评测「{{ examInfo.examName }}」创建成功 请选择样卷</v-card-title
+          >评测「{{ examInfo.examName }}」创建成功 请设置试卷内容</v-card-title
         >
         <v-card-subtitle>评测ID: {{ examInfo.examId }} </v-card-subtitle>
         <v-card-text>
@@ -48,38 +48,75 @@
           </v-row>
         </v-card-text>
       </v-card>
-      <v-container> </v-container>
-      <v-toolbar flat
-        ><v-toolbar-title>
-          样卷选择
-        </v-toolbar-title>
-        <template v-slot:extension>
-          <v-tabs v-model="tab" v-if="examSamples[0]" align-with-title>
-            <v-tab v-for="i in examSamples.length" :key="i">
-              样卷 {{ i }}
-            </v-tab>
-          </v-tabs>
-        </template>
-      </v-toolbar>
+      <div v-if="!paperQuestionsSet">
+        <v-toolbar flat>
+          <v-toolbar-title>
+            样卷选择
+          </v-toolbar-title>
+          <template v-slot:extension>
+            <v-tabs v-model="tab" v-if="examSamples[0]" align-with-title>
+              <v-tab v-for="i in examSamples.length" :key="i" class="ml-2">
+                样卷 {{ i }}
+              </v-tab>
+            </v-tabs>
+          </template>
+        </v-toolbar>
+        <v-tabs-items v-model="tab">
+          <v-tab-item v-for="sample in examSamples" :key="sample.id">
+            <div v-if="!fetching">
+              <exam-questions-info
+                :exam-questions="sampleQuestions[tab]"
+              ></exam-questions-info>
+            </div>
+            <v-row class="d-flex justify-center my-3">
+              <v-btn
+                class="primary align-self-center ma-2 text-center"
+                @click="onSelectSample(sample.id)"
+              >
+                选择此试卷
+              </v-btn>
+              <v-btn
+                class="secondary align-self-center ma-2 text-center"
+                @click="showQuestionList = true"
+              >
+                基于样卷调整试题
+              </v-btn>
+            </v-row>
+          </v-tab-item>
+        </v-tabs-items>
+      </div>
+      <v-card v-else>
+        <v-card-title class="d-inline-block display-1">试卷内容</v-card-title>
+        <v-btn
+          small
+          fab
+          elevation="0"
+          v-if="fetching"
+          :loading="fetching"
+          class="mb-4 d-inline-block"
+        >
+          <v-icon class="mb-4" v-if="fetching">mdi-cached</v-icon>
+        </v-btn>
+        <v-btn
+          v-else
+          class="d-inline-block secondary align-self-center ma-2 text-center pa-1 mb-6"
+          @click="showQuestionList = true"
+        >
+          调整试题
+        </v-btn>
 
-      <v-tabs-items v-model="tab">
-        <v-tab-item v-for="sample in examSamples" :key="sample.id">
-          <div v-if="!fetching" style="min-height: 50vh">
-            <exam-questions-info
-              :exam-questions="sampleQuestions[tab]"
-            ></exam-questions-info>
-          </div>
-          <v-row class="d-flex justify-center my-3">
-            <v-btn
-              class="primary align-self-center my-2 text-center"
-              @click="onSelectSample(sample.id)"
-            >
-              选择
-            </v-btn>
-          </v-row>
-        </v-tab-item>
-      </v-tabs-items>
+        <exam-questions-info
+          :exam-questions="examQuestions"
+        ></exam-questions-info>
+      </v-card>
+      <v-divider></v-divider>
     </v-container>
+    <question-list
+      v-if="showQuestionList"
+      :sample-questions="curQuestions"
+      :question-and-score="curQuestionAndScore"
+      @leave="showQuestionList = false"
+    ></question-list>
     <v-snackbar v-model="success" color="success" top>
       设置样卷成功
     </v-snackbar>
@@ -96,7 +133,8 @@ import {
   TeacherQuestionSerializer,
   ID,
   ReceivedData,
-  SampleSerializer
+  SampleSerializer,
+  QuestionAndScore
 } from '@/api/types'
 import {
   getExamById,
@@ -107,12 +145,14 @@ import { getQuestionInfo, getSamplePaper } from '@/api/question'
 import { AxiosResponse } from 'axios'
 import PageHeader from '@/views/components/PageHeader.vue'
 import ExamQuestionsInfo from '@/views/components/ExamQuestionsInfo.vue'
+import QuestionList from '@/views/teacher/components/QuestionList.vue'
 
 export default Vue.extend({
   name: 'EditExam',
   components: {
     PageHeader,
-    ExamQuestionsInfo
+    ExamQuestionsInfo,
+    QuestionList
   },
   data() {
     return {
@@ -120,10 +160,13 @@ export default Vue.extend({
       tab: 0,
       examSamples: [] as Array<SampleSerializer>,
       sampleQuestions: [] as Array<Array<TeacherQuestionSerializer>>,
+      examQuestions: [] as Array<TeacherQuestionSerializer>,
+      examQuestionAndScore: {} as QuestionAndScore,
       fetching: false,
-      isSampleSet: false,
+      paperQuestionsSet: false,
       success: false,
       failed: false,
+      showQuestionList: false,
       breadcrumb: [
         {
           text: '主页',
@@ -148,6 +191,20 @@ export default Vue.extend({
       this.fetchQuestionInfo(val)
     }
   },
+  computed: {
+    curQuestionAndScore(): QuestionAndScore {
+      if (this.paperQuestionsSet) {
+        return this.examQuestionAndScore
+      }
+      return this.examSamples[this.tab].questionAndScore
+    },
+    curQuestions(): Array<TeacherQuestionSerializer> {
+      if (this.paperQuestionsSet) {
+        return this.examQuestions
+      }
+      return this.sampleQuestions[this.tab]
+    }
+  },
   methods: {
     fetchQuestionInfo(val: number) {
       if (this.sampleQuestions[val]) {
@@ -201,14 +258,37 @@ export default Vue.extend({
 
     getExamQuestions(examId ?? '')
       .then(({ data }) => {
-        this.isSampleSet = true
-      })
-      .catch(({ data }) => {})
+        this.paperQuestionsSet = true
+        this.examQuestionAndScore = data.result.questionAndScore
+        let questionsIds = Object.keys(data.result.questionAndScore)
+        let questionsPromises: Promise<
+          AxiosResponse<ReceivedData<TeacherQuestionSerializer, 0>>
+        >[] = []
+
+        this.fetching = true
+        questionsIds.forEach((v, ind) => {
+          questionsPromises[ind] = getQuestionInfo(v)
+        })
 
-    getSamplePaper().then(({ data }) => {
-      this.examSamples = data.result
-      this.fetchQuestionInfo(0)
-    })
+        Promise.all(questionsPromises)
+          .then((reps) => {
+            this.examQuestions = reps.map((value) => {
+              return value.data.result
+            })
+          })
+          .catch((error) => {
+            console.log('题目获取失败')
+          })
+          .finally(() => {
+            this.fetching = false
+          })
+      })
+      .catch(() => {
+        getSamplePaper().then(({ data }) => {
+          this.examSamples = data.result
+          this.fetchQuestionInfo(0)
+        })
+      })
   }
 })
 </script>

+ 202 - 99
src/views/teacher/components/QuestionList.vue

@@ -1,122 +1,225 @@
 <template>
   <v-container>
-    <v-card>
-      <!--      <v-card-title>已选题目</v-card-title>-->
-      <!--      <v-list class="px-10 flex">-->
-      <!--        <v-list-item-group multiple>-->
-      <!--          <v-divider></v-divider>-->
-      <!--          <template v-for="question in selectedQuestions">-->
-      <!--            <v-list-item :key="question.id" :value="question.id">-->
-      <!--              <template v-slot:default="{ active }">-->
-      <!--                <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">-->
-      <!--                    {{ question.stem }}</v-list-item-title-->
-      <!--                  >-->
-      <!--                </v-list-item-content>-->
-      <!--              </template>-->
-      <!--            </v-list-item>-->
-      <!--            <v-divider :key="' ' + question.id"></v-divider>-->
-      <!--          </template>-->
-      <!--        </v-list-item-group>-->
-      <!--      </v-list>-->
-      <!--      <v-card-title>题目列表</v-card-title>-->
-      <!--      <v-list class="px-10 flex">-->
-      <!--        <v-list-item-group v-model="selectedId" multiple>-->
-      <!--          <v-divider></v-divider>-->
-      <!--          <template v-for="question in questions">-->
-      <!--            <v-list-item :key="question.id" :value="question.id">-->
-      <!--              <template v-slot:default="{ active }">-->
-      <!--                <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">-->
-      <!--                    {{ question.stem }}</v-list-item-title-->
-      <!--                  >-->
-      <!--                </v-list-item-content>-->
-      <!--              </template>-->
-      <!--            </v-list-item>-->
-      <!--            <v-divider :key="' ' + question.id"></v-divider>-->
-      <!--          </template>-->
-      <!--        </v-list-item-group>-->
-      <!--        <v-card-actions>-->
-      <!--          <v-btn color="primary" @click="fetchData">下一页</v-btn>-->
-      <!--        </v-card-actions>-->
-      <!--      </v-list>-->
-      <v-data-table
-        v-model="selectedId"
-        item-key="id"
-        show-select
-        :headers="headers"
-        :items="questions"
-        show-expand
-        style="width: 50vw"
-        class="text-wrap"
-      >
-        <template v-slot:expanded-item="{ headers, item }">
-          <td :colspan="headers.length">
-            {{ item.stem }}
-          </td>
-        </template>
-      </v-data-table>
-    </v-card>
+    <v-card-title>已选题目</v-card-title>
+    <v-data-table
+      item-key="id"
+      :headers="selectedHeaders"
+      :items="selectedQuestions"
+      no-data-text="空"
+    >
+      <template v-slot:item.score="{ item }">
+        <div class="d-flex">
+          <span class="align-self-center mx-1">{{ item.score }}</span>
+          <span class="d-flex flex-column">
+            <v-icon small @click="increasePoints(item)">
+              mdi-arrow-up-bold
+            </v-icon>
+            <v-icon small @click="decreasePoints(item)">
+              mdi-arrow-down-bold
+            </v-icon>
+          </span>
+        </div>
+      </template>
+      <template v-slot:item.actions="{ item }">
+        <v-icon @click="deleteItem(item)">
+          mdi-delete
+        </v-icon>
+      </template>
+      <template v-slot:item.stem="{ item }">
+        <markdown-text :raw="item.stem"></markdown-text>
+      </template>
+    </v-data-table>
+    <v-row class="ma-0 mt-5" justify="center">
+      <v-btn class="primary mx-2" @click="onSubmitManualPaper">确认调整</v-btn>
+      <v-btn class="secondary mx-2" @click="onLeave">返回</v-btn>
+    </v-row>
+    <v-row class="mx-2 mt-5" align="center">
+      <v-col cols="3">
+        <v-text-field v-model="stem" label="根据题目内容查找"></v-text-field>
+      </v-col>
+      <v-col cols="3">
+        <v-text-field v-model="tags" label="标签"></v-text-field>
+      </v-col>
+      <v-col cols="3">
+        <v-text-field v-model="knowledgeName" label="知识点"></v-text-field>
+      </v-col>
+      <v-col cols="1">
+        <v-btn class="primary mb-4 info" @click="onSearch">搜索</v-btn>
+      </v-col>
+    </v-row>
+
+    <v-card-title>试题列表</v-card-title>
+    <v-data-table
+      v-model="selectedQuestions"
+      item-key="id"
+      show-select
+      :headers="headers"
+      :items="questions"
+      show-expand
+      class="text-wrap"
+      :loading="loading"
+      @item-selected="onSelectQuestion"
+      @toggle-select-all="onToggleSelectAll"
+      hide-default-footer
+      :page.sync="pageNum"
+    >
+      <template v-slot:expanded-item="{ headers, item }">
+        <td :colspan="headers.length">
+          <markdown-text :raw="item.stem"></markdown-text>
+        </td>
+      </template>
+    </v-data-table>
+    <v-pagination
+      total-visible="5"
+      class="my-2"
+      :length="Math.ceil(totalNum / pageSize)"
+      v-model="pageNum"
+      @input="changePage"
+    ></v-pagination>
+    <v-snackbar v-model="selectQuestionsSuccess" top color="info">
+      设置试题成功
+    </v-snackbar>
   </v-container>
 </template>
 
 <script lang="ts">
 import Vue, { PropType } from 'vue'
-import { QuestionSerializer, ID } from '@/api/types'
+import { QuestionSerializer } from '@/api/types'
+import { getQuestionList } from '@/api/question'
+import { questionTypeToText } from '@/utils/common'
+import MarkdownText from '@/views/exam/components/MarkdownText.vue'
+import { addExamPaperQuestions } from '@/api/exam'
+
+interface Question extends QuestionSerializer {
+  typeText: string
+  score: number
+}
 
 export default Vue.extend({
   name: 'QuestionList',
-  props: {},
+  props: {
+    sampleQuestions: {
+      type: Array as PropType<Array<QuestionSerializer>>
+    },
+    questionAndScore: {
+      type: Object
+    }
+  },
+  components: {
+    MarkdownText
+  },
   data() {
     return {
-      questions: [] as Array<QuestionSerializer>,
-      selectedId: [] as Array<ID>,
-      selectedQuestions: [] as Array<QuestionSerializer>,
+      questions: [] as Array<Question>,
+      selectedQuestions: [] as Array<Question>,
+      tags: '',
+      knowledgeName: '',
+      stem: '',
+      pageNum: 1,
+      pageSize: 10,
+      totalNum: 0,
+      loading: true,
+      selectQuestionsSuccess: false,
       headers: [
-        { text: '技能点', value: 'skillId' },
-        { text: '知识点', value: 'knowledgeId' },
-        // { text: '题干', value: 'stem', sortable: false },
-        { text: '题干', value: 'data-table-expand' }
+        { text: '题目类型', value: 'typeText' },
+        { text: '关键词', value: 'keyPoints', sortable: false },
+        { text: '知识点', value: 'knowledgeId', sortable: false },
+        { text: '标签', value: 'tags', sortable: false },
+        { text: '', value: 'data-table-expand' }
+      ],
+      selectedHeaders: [
+        { text: '分值', value: 'score', width: 100 },
+        { text: '操作', value: 'actions', sortable: false, width: 100 },
+        { text: '题干', value: 'stem', sortable: false }
       ]
     }
   },
-  watch: {
-    selectedId: {
-      deep: true,
-      handler: function(newVal: Array<ID>, oldVal: Array<ID>): void {
-        if (newVal.length > oldVal.length) {
-          const changedId = newVal[newVal.length - 1]
-          const changeQuestion = this.questions.find((val) => {
-            return val.id === changedId
-          })
-          if (changeQuestion !== undefined) {
-            this.selectedQuestions.push(changeQuestion)
-          }
-        } else {
-          const changeId = oldVal.filter((val) => {
-            return !newVal.find((value) => {
-              return value === val
-            })
-          })[0]
-          this.selectedQuestions = this.selectedQuestions.filter((val) => {
-            return val.id !== changeId
-          })
-        }
-      }
-    }
-  },
   created() {
     this.fetchData()
   },
-  mounted() {},
+  mounted() {
+    this.selectedQuestions = this.sampleQuestions.map((val) => {
+      return {
+        ...val,
+        score: this.questionAndScore[val.id] ?? 3,
+        typeText: questionTypeToText(val.type)
+      }
+    })
+  },
   methods: {
-    fetchData() {}
+    fetchData() {
+      getQuestionList(
+        { pageSize: this.pageSize, pageNum: this.pageNum },
+        this.stem,
+        this.tags,
+        this.knowledgeName
+      ).then(({ data }) => {
+        this.questions = data.result.questionStemList.map((val) => {
+          return { ...val, typeText: questionTypeToText(val.type), score: 5 }
+        })
+        this.totalNum = data.result.totalNum
+        this.loading = false
+      })
+    },
+    deleteItem(item: Question) {
+      let selectedIndex = this.selectedQuestions.indexOf(item)
+      this.selectedQuestions.splice(selectedIndex, 1)
+    },
+    increasePoints(item: Question) {
+      let selectedIndex = this.selectedQuestions.indexOf(item)
+      this.selectedQuestions[selectedIndex].score++
+    },
+    decreasePoints(item: Question) {
+      let selectedIndex = this.selectedQuestions.indexOf(item)
+      if (this.selectedQuestions[selectedIndex].score > 0)
+        this.selectedQuestions[selectedIndex].score--
+    },
+    onSelectQuestion(event: { item: Question; value: boolean }) {
+      if (event.value) {
+        if (
+          this.selectedQuestions.filter((val) => {
+            return val.id === event.item.id
+          }).length === 0
+        ) {
+          this.selectedQuestions.push(event.item)
+        }
+      } else {
+        this.selectedQuestions = this.selectedQuestions.filter((val) => {
+          return val.id !== event.item.id
+        })
+      }
+    },
+    onToggleSelectAll(event: { items: any[]; value: boolean }) {
+      event.items.forEach((val) => {
+        this.onSelectQuestion({ item: val, value: event.value })
+      })
+    },
+    onSubmitManualPaper() {
+      let examId = this.$route.params.examId
+
+      let idAndScore = new Map()
+      this.selectedQuestions.forEach((val) => {
+        idAndScore.set(val.id, val.score)
+      })
+
+      addExamPaperQuestions(examId, Object.fromEntries(idAndScore)).then(() => {
+        this.selectQuestionsSuccess = true
+        setTimeout(() => {
+          this.$router.push({ name: 'teacher-exam-details' })
+        }, 1000)
+      })
+    },
+    changePage() {
+      this.loading = true
+      this.fetchData()
+    },
+    onSearch() {
+      this.pageNum = 1
+      this.fetchData()
+    },
+    onLeave() {
+      this.$emit('leave')
+    }
   }
 })
 </script>