@ -9,14 +9,14 @@
< / header >
< / header >
< nav v-if ="!inProgress" class="prototype-nav" aria-label="考试编辑步骤" >
< nav v-if ="!inProgress" class="prototype-nav" aria-label="考试编辑步骤" >
< button type = "button" class = "step-node" : class = "{ 'is-active': activeStep === 0 }" @ click = "activeStep = 0" > < span class = "step-num" > 01 < / span > 考试设置 < / button >
< button type = "button" class = "step-node" : class = "{ 'is-active': activeStep === 0 }" @ click = "activeStep = 0" > < span class = "step-num" > 01 < / span > 添加试题 < / button >
< el -icon class = "step-arrow" > < ArrowRight / > < / e l - i c o n >
< el -icon class = "step-arrow" > < ArrowRight / > < / e l - i c o n >
< button type = "button" class = "step-node" : class = "{ 'is-active': activeStep === 1 }" @ click = "activeStep = 1" > < span class = "step-num" > 02 < / span > 添加试题 < / button >
< button type = "button" class = "step-node" : class = "{ 'is-active': activeStep === 1 }" @ click = "activeStep = 1" > < span class = "step-num" > 02 < / span > 试卷概览 < / button >
< el -icon class = "step-arrow" > < ArrowRight / > < / e l - i c o n >
< el -icon class = "step-arrow" > < ArrowRight / > < / e l - i c o n >
< button type = "button" class = "step-node" : class = "{ 'is-active': activeStep === 2 }" @ click = "activeStep = 2" > < span class = "step-num" > 03 < / span > 试卷预览 < / button >
< button type = "button" class = "step-node" : class = "{ 'is-active': activeStep === 2 }" @ click = "activeStep = 2" > < span class = "step-num" > 03 < / span > 考试设置 < / button >
< / nav >
< / nav >
< section v-if ="activeStep === 0 || activeStep === 1 " class="editor-panel prototype-base-panel">
< section v-if ="activeStep === 2 " class="editor-panel prototype-base-panel">
< div class = "section-card" >
< div class = "section-card" >
< header class = "card-head" > < div > < h2 > 基础设置 < / h2 > < p > 填写考试名称 、 时间与班级 , 分值矩阵自动汇总 < / p > < / div > < / header >
< header class = "card-head" > < div > < h2 > 基础设置 < / h2 > < p > 填写考试名称 、 时间与班级 , 分值矩阵自动汇总 < / p > < / div > < / header >
< el -form ref = "formRef" :model ="form" :rules ="rules" label -width = " 100px " class = "base-form" >
< el -form ref = "formRef" :model ="form" :rules ="rules" label -width = " 100px " class = "base-form" >
@ -53,7 +53,7 @@
< / div >
< / div >
< / section >
< / section >
< section v-if ="!inProgress && activeStep === 0 " class="editor-panel settings-stage">
< section v-if ="!inProgress && activeStep === 2 " class="editor-panel settings-stage">
< div class = "section-card" >
< div class = "section-card" >
< header class = "card-head" > < div > < h2 > 考试设置 < / h2 > < p > 设置试题展示 、 作答行为和多选题计分方式 < / p > < / div > < / header >
< header class = "card-head" > < div > < h2 > 考试设置 < / h2 > < p > 设置试题展示 、 作答行为和多选题计分方式 < / p > < / div > < / header >
< div class = "settings-grid" >
< div class = "settings-grid" >
@ -103,7 +103,7 @@
< / div >
< / div >
< / section >
< / section >
< section v-if ="!inProgress && activeStep === 1 " class="editor-panel question-stage">
< section v-if ="!inProgress && activeStep === 0 " class="editor-panel question-stage">
< div class = "section-card" >
< div class = "section-card" >
< header class = "section-head" > < div > < h2 > 选择试题 < / h2 > < p > 勾选题目并设置该题分值 , 选择结果实时同步到试卷概览 < / p > < / div >
< header class = "section-head" > < div > < h2 > 选择试题 < / h2 > < p > 勾选题目并设置该题分值 , 选择结果实时同步到试卷概览 < / p > < / div >
< div class = "import-tools" >
< div class = "import-tools" >
@ -148,7 +148,7 @@
< / div >
< / div >
< / section >
< / section >
< section v-if ="!inProgress && activeStep === 2 " class="editor-panel preview-stage">
< section v-if ="!inProgress && activeStep === 1 " class="editor-panel preview-stage">
< div class = "section-card preview-summary-card" >
< div class = "section-card preview-summary-card" >
< header class = "section-head" > < div > < h2 > 试卷概览 < / h2 > < p > { { form . questions . length } } 道题 , 共 { { totalScore } } 分 < / p > < / div > < el -button :icon ="Download" @click ="exportPaper" :disabled ="isNew" > 导出 Word 试卷 < / e l - b u t t o n > < / h e a d e r >
< header class = "section-head" > < div > < h2 > 试卷概览 < / h2 > < p > { { form . questions . length } } 道题 , 共 { { totalScore } } 分 < / p > < / div > < el -button :icon ="Download" @click ="exportPaper" :disabled ="isNew" > 导出 Word 试卷 < / e l - b u t t o n > < / h e a d e r >
< div class = "score-cards preview-score-cards" >
< div class = "score-cards preview-score-cards" >
@ -214,7 +214,7 @@ import { saveAs } from "file-saver";
import * as examApi from "@/api/exam" ;
import * as examApi from "@/api/exam" ;
const route = useRoute ( ) ; const router = useRouter ( ) ; const id = computed ( ( ) => route . params . id ) ; const isNew = computed ( ( ) => id . value === "new" ) ;
const route = useRoute ( ) ; const router = useRouter ( ) ; const id = computed ( ( ) => route . params . id ) ; const isNew = computed ( ( ) => id . value === "new" ) ;
const loading = ref ( false ) ; const activeStep = ref ( 1 ) ; const activeType = ref ( "SINGLE" ) ; const status = ref ( "DRAFT" ) ; const classes = ref ( [ ] ) ; const bank = ref ( [ ] ) ; const formRef = ref ( ) ; const duration = ref ( 60 ) ;
const loading = ref ( false ) ; const activeStep = ref ( 0 ) ; const activeType = ref ( "SINGLE" ) ; const status = ref ( "DRAFT" ) ; const classes = ref ( [ ] ) ; const bank = ref ( [ ] ) ; const formRef = ref ( ) ; const duration = ref ( 60 ) ;
const typeOptions = [ { label : "单选题" , value : "SINGLE" } , { label : "多选题" , value : "MULTIPLE" } , { label : "判断题" , value : "TRUE_FALSE" } , { label : "简答题" , value : "SHORT_ANSWER" } ] ;
const typeOptions = [ { label : "单选题" , value : "SINGLE" } , { label : "多选题" , value : "MULTIPLE" } , { label : "判断题" , value : "TRUE_FALSE" } , { label : "简答题" , value : "SHORT_ANSWER" } ] ;
const statusMeta = { DRAFT : { label : "未发布" , tone : "info" } , NOT _STARTED : { label : "未开始" , tone : "" } , IN _PROGRESS : { label : "进行中" , tone : "success" } , ENDED : { label : "已结束" , tone : "warning" } } ;
const statusMeta = { DRAFT : { label : "未发布" , tone : "info" } , NOT _STARTED : { label : "未开始" , tone : "" } , IN _PROGRESS : { label : "进行中" , tone : "success" } , ENDED : { label : "已结束" , tone : "warning" } } ;
const form = reactive ( { name : "" , startTime : "" , endTime : "" , classIds : [ ] , questionOrderMode : "FIXED" , optionOrderMode : "FIXED" , allowCopyQuestion : false , allowPasteAnswer : false , allowRollback : true , multipleScoreMode : "ALL_OR_NOTHING" , questions : [ ] } ) ;
const form = reactive ( { name : "" , startTime : "" , endTime : "" , classIds : [ ] , questionOrderMode : "FIXED" , optionOrderMode : "FIXED" , allowCopyQuestion : false , allowPasteAnswer : false , allowRollback : true , multipleScoreMode : "ALL_OR_NOTHING" , questions : [ ] } ) ;
@ -235,8 +235,8 @@ function selectedByType(type) { const ids = new Set(bank.value.filter((item) =>
function questionCount ( type ) { return bank . value . filter ( ( item ) => item . type === type ) . length ; }
function questionCount ( type ) { return bank . value . filter ( ( item ) => item . type === type ) . length ; }
function scoreByType ( type ) { return selectedQuestions ( type ) . reduce ( ( sum , item ) => sum + Number ( selectedScore ( item . id ) ) , 0 ) ; }
function scoreByType ( type ) { return selectedQuestions ( type ) . reduce ( ( sum , item ) => sum + Number ( selectedScore ( item . id ) ) , 0 ) ; }
function selectedQuestions ( type ) { const selected = new Set ( form . questions . map ( ( item ) => item . questionId ) ) ; return bank . value . filter ( ( item ) => item . type === type && selected . has ( item . id ) ) ; }
function selectedQuestions ( type ) { const selected = new Set ( form . questions . map ( ( item ) => item . questionId ) ) ; return bank . value . filter ( ( item ) => item . type === type && selected . has ( item . id ) ) ; }
async function nextStep ( ) { if ( activeStep . value === 0 ) { try { await formRef . value . validate ( ) ; const start = new Date ( form . startTime . replace ( " " , "T" ) ) ; const end = new Date ( form . endTime . replace ( " " , "T" ) ) ; if ( end <= start ) return ElMessage . warning ( "结束时间必须晚于开始时间" ) ; duration . value = Math . round ( ( end - start ) / 60000 ) ; } catch { return ; } } if ( activeStep . value === 1 && ! form . questions . length ) return ElMessage . warning ( "至少选择一道试题" ) ; activeStep . value ++ ; }
async function nextStep ( ) { if ( activeStep . value === 0 && ! form . questions . length ) return ElMessage . warning ( "至少选择一道试题" ) ; if ( activeStep . value === 2 ) { try { await formRef . value . validate ( ) ; const start = new Date ( form . startTime . replace ( " " , "T" ) ) ; const end = new Date ( form . endTime . replace ( " " , "T" ) ) ; if ( end <= start ) return ElMessage . warning ( "结束时间必须晚于开始时间" ) ; duration . value = Math . round ( ( end - start ) / 60000 ) ; } catch { return ; } } activeStep . value ++ ; }
async function save ( publish ) { if ( inProgress . value ) { await examApi . updateExam ( id . value , form ) ; ElMessage . success ( "结束时间已更新" ) ; return router . push ( "/exam/index" ) ; } try { await formRef . value ? . validate ( ) ; } catch { activeStep . value = 0 ; return ; } if ( ! form . questions . length ) { activeStep . value = 1 ; return ElMessage . warning ( "至少选择一道试题" ) ; } const result = isNew . value ? await examApi . createExam ( form ) : await examApi . updateExam ( id . value , form ) ; const examId = result . data . id ; if ( publish ) await examApi . publishExam ( examId ) ; ElMessage . success ( publish ? "考试已保存并发布" : "考试已保存" ) ; router . push ( "/exam/index" ) ; }
async function save ( publish ) { if ( inProgress . value ) { await examApi . updateExam ( id . value , form ) ; ElMessage . success ( "结束时间已更新" ) ; return router . push ( "/exam/index" ) ; } try { await formRef . value ? . validate ( ) ; } catch { activeStep . value = 2 ; return ; } if ( ! form . questions . length ) { activeStep . value = 0 ; return ElMessage . warning ( "至少选择一道试题" ) ; } const result = isNew . value ? await examApi . createExam ( form ) : await examApi . updateExam ( id . value , form ) ; const examId = result . data . id ; if ( publish ) await examApi . publishExam ( examId ) ; ElMessage . success ( publish ? "考试已保存并发布" : "考试已保存" ) ; router . push ( "/exam/index" ) ; }
async function importPaperQuestions ( { file } ) { const formData = new FormData ( ) ; formData . append ( "file" , file ) ; const { data } = await examApi . importQuestions ( formData ) ; bank . value = [ ... data , ... bank . value ] ; for ( const q of data ) if ( ! isSelected ( q . id ) ) form . questions . push ( { questionId : q . id , score : 1 } ) ; ElMessage . success ( ` 导入并选中 ${ data . length } 道题 ` ) ; }
async function importPaperQuestions ( { file } ) { const formData = new FormData ( ) ; formData . append ( "file" , file ) ; const { data } = await examApi . importQuestions ( formData ) ; bank . value = [ ... data , ... bank . value ] ; for ( const q of data ) if ( ! isSelected ( q . id ) ) form . questions . push ( { questionId : q . id , score : 1 } ) ; ElMessage . success ( ` 导入并选中 ${ data . length } 道题 ` ) ; }
async function downloadTemplate ( ) { saveAs ( await examApi . downloadQuestionTemplate ( ) , "考试题库导入模板.docx" ) ; }
async function downloadTemplate ( ) { saveAs ( await examApi . downloadQuestionTemplate ( ) , "考试题库导入模板.docx" ) ; }
async function exportPaper ( ) { saveAs ( await examApi . exportExamPaper ( id . value ) , ` ${ form . name } 试卷.docx ` ) ; }
async function exportPaper ( ) { saveAs ( await examApi . exportExamPaper ( id . value ) , ` ${ form . name } 试卷.docx ` ) ; }