|
|
@@ -0,0 +1,374 @@
|
|
|
+<template>
|
|
|
+ <div class="ele-body question-list-page">
|
|
|
+ <el-card shadow="never">
|
|
|
+ <!-- 顶部筛选 -->
|
|
|
+ <div class="filter-bar">
|
|
|
+ <el-form :inline="true" :model="filter" size="small" @submit.native.prevent>
|
|
|
+ <el-form-item label="题库名称:">
|
|
|
+ <el-input
|
|
|
+ v-model="filter.questionBankName"
|
|
|
+ placeholder="支持模糊查询"
|
|
|
+ clearable
|
|
|
+ style="width: 200px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="试题内容:">
|
|
|
+ <el-input
|
|
|
+ v-model="filter.questionContent"
|
|
|
+ placeholder="支持模糊查询"
|
|
|
+ clearable
|
|
|
+ style="width: 220px"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="试题类型:">
|
|
|
+ <el-select
|
|
|
+ v-model="filter.questionType"
|
|
|
+ placeholder="全部"
|
|
|
+ clearable
|
|
|
+ style="width: 160px"
|
|
|
+ >
|
|
|
+ <el-option label="单选题" value="SINGLE" />
|
|
|
+ <el-option label="多选题" value="MULTIPLE" />
|
|
|
+ <el-option label="判断题" value="JUDGE" />
|
|
|
+ <el-option label="问答题" value="QUESTION_ANSWER" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" icon="el-icon-search" @click="handleSearch"
|
|
|
+ >查询</el-button
|
|
|
+ >
|
|
|
+ <el-button icon="el-icon-refresh" @click="handleReset"
|
|
|
+ >重置</el-button
|
|
|
+ >
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 顶部信息 -->
|
|
|
+ <div class="page-meta">
|
|
|
+ <span>题库:{{ questionBankName || '-' }}</span>
|
|
|
+ <span>共 {{ total }} 条</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 试题列表 -->
|
|
|
+ <div v-loading="loading" class="question-list">
|
|
|
+ <el-empty v-if="!loading && list.length === 0" description="暂无试题数据" />
|
|
|
+ <el-card
|
|
|
+ v-for="(item, index) in list"
|
|
|
+ :key="item.id || index"
|
|
|
+ shadow="hover"
|
|
|
+ class="question-item"
|
|
|
+ >
|
|
|
+ <div class="question-header">
|
|
|
+ <span class="question-no">第 {{ item.sortNo || index + 1 }} 题</span>
|
|
|
+ <el-tag :type="typeTagType(item.questionType)" size="mini">
|
|
|
+ {{ typeLabel(item.questionType) }}
|
|
|
+ </el-tag>
|
|
|
+ <span class="question-score">分值:{{ item.questionScore ?? 0 }}</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="question-title">
|
|
|
+ <span class="label">题目:</span>
|
|
|
+ <span class="content">{{ item.questionContent || '-' }}</span>
|
|
|
+ <el-image
|
|
|
+ v-if="item.questionImage"
|
|
|
+ class="question-image"
|
|
|
+ :src="resolveImageUrl(item.questionImage)"
|
|
|
+ :preview-src-list="[resolveImageUrl(item.questionImage)]"
|
|
|
+ fit="cover"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 选项 -->
|
|
|
+ <div
|
|
|
+ v-if="item.optionList && item.optionList.length"
|
|
|
+ class="question-options"
|
|
|
+ >
|
|
|
+ <div class="label">选项:</div>
|
|
|
+ <div class="options">
|
|
|
+ <div
|
|
|
+ v-for="(opt, optIndex) in item.optionList"
|
|
|
+ :key="opt.id"
|
|
|
+ class="option-row"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ v-if="item.questionType !== 'QUESTION_ANSWER'"
|
|
|
+ class="option-key"
|
|
|
+ >{{ optionPrefix(optIndex) }}.</span
|
|
|
+ >
|
|
|
+ <span class="option-content">{{ opt.optionContent || '-' }}</span>
|
|
|
+ <el-image
|
|
|
+ v-if="opt.optionImage"
|
|
|
+ class="option-image"
|
|
|
+ :src="resolveImageUrl(opt.optionImage)"
|
|
|
+ :preview-src-list="[resolveImageUrl(opt.optionImage)]"
|
|
|
+ fit="cover"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 答案 -->
|
|
|
+ <div class="question-answer">
|
|
|
+ <span class="label">答案:</span>
|
|
|
+ <span class="content answer-text">{{
|
|
|
+ formatAnswer(item)
|
|
|
+ }}</span>
|
|
|
+ <span v-if="item.lessScore" class="less-score">
|
|
|
+ 少选得分:{{ item.lessScore }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 解析 -->
|
|
|
+ <div v-if="item.questionExplanation" class="question-explanation">
|
|
|
+ <span class="label">解析:</span>
|
|
|
+ <span class="content">{{ item.questionExplanation }}</span>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分页 -->
|
|
|
+ <div class="pagination-wrapper">
|
|
|
+ <el-pagination
|
|
|
+ background
|
|
|
+ layout="total, prev, pager, next, jumper"
|
|
|
+ :total="total"
|
|
|
+ :current-page.sync="filter.pageNum"
|
|
|
+ :page-size="filter.size"
|
|
|
+ @current-change="loadList"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+ import { pageQuestion } from '@/api/questionBank/questionBank.js';
|
|
|
+ import { getImageUrl } from '@/utils/file';
|
|
|
+
|
|
|
+ const TYPE_MAP = {
|
|
|
+ SINGLE: '单选题',
|
|
|
+ MULTIPLE: '多选题',
|
|
|
+ JUDGE: '判断题',
|
|
|
+ QUESTION_ANSWER: '问答题'
|
|
|
+ };
|
|
|
+
|
|
|
+ const TYPE_TAG_TYPE = {
|
|
|
+ SINGLE: '',
|
|
|
+ MULTIPLE: 'success',
|
|
|
+ JUDGE: 'warning',
|
|
|
+ QUESTION_ANSWER: 'info'
|
|
|
+ };
|
|
|
+
|
|
|
+ export default {
|
|
|
+ name: 'QuestionBankQuestionList',
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: false,
|
|
|
+ list: [],
|
|
|
+ total: 0,
|
|
|
+ questionBankId: '',
|
|
|
+ questionBankName: '',
|
|
|
+ filter: {
|
|
|
+ questionBankId: '',
|
|
|
+ questionBankName: '',
|
|
|
+ questionContent: '',
|
|
|
+ questionType: '',
|
|
|
+ pageNum: 1,
|
|
|
+ size: 10
|
|
|
+ }
|
|
|
+ };
|
|
|
+ },
|
|
|
+ created() {
|
|
|
+ const { questionBankId, questionBankName } = this.$route.query;
|
|
|
+ this.questionBankId = questionBankId || '';
|
|
|
+ this.questionBankName = questionBankName || '';
|
|
|
+ this.filter.questionBankId = this.questionBankId;
|
|
|
+ this.filter.questionBankName = this.questionBankName;
|
|
|
+ this.loadList();
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ typeLabel(type) {
|
|
|
+ return TYPE_MAP[type] || type || '-';
|
|
|
+ },
|
|
|
+ typeTagType(type) {
|
|
|
+ return TYPE_TAG_TYPE[type] || '';
|
|
|
+ },
|
|
|
+ formatAnswer(item) {
|
|
|
+ const answer = item?.correctAnswer;
|
|
|
+ if (!answer) return '-';
|
|
|
+ const type = item?.questionType || '';
|
|
|
+ if (type === 'JUDGE') {
|
|
|
+ return /^(true|1|A|正确)$/i.test(String(answer)) ? '正确' : '错误';
|
|
|
+ }
|
|
|
+ if (type === 'MULTIPLE' && typeof answer === 'string') {
|
|
|
+ return answer
|
|
|
+ .split(',')
|
|
|
+ .map((s) => s.trim())
|
|
|
+ .filter(Boolean)
|
|
|
+ .join('、');
|
|
|
+ }
|
|
|
+ return answer;
|
|
|
+ },
|
|
|
+ /* 选项序号:单选/多选/判断返回 A、B、C...,问答题不显示 */
|
|
|
+ optionPrefix(index) {
|
|
|
+ return String.fromCharCode(65 + index);
|
|
|
+ },
|
|
|
+ /* storePath 拼接为可访问的图片地址 */
|
|
|
+ resolveImageUrl(storePath) {
|
|
|
+ if(storePath.startsWith('http')) {
|
|
|
+ return storePath;
|
|
|
+ }
|
|
|
+ return storePath ? getImageUrl(storePath) : '';
|
|
|
+ },
|
|
|
+ async loadList() {
|
|
|
+ if (!this.filter.questionBankId) {
|
|
|
+ this.$message.warning('缺少题库ID参数');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.loading = true;
|
|
|
+ try {
|
|
|
+ const data = await pageQuestion({
|
|
|
+ questionBankId: this.filter.questionBankId,
|
|
|
+ questionBankName: this.filter.questionBankName || undefined,
|
|
|
+ questionContent: this.filter.questionContent || undefined,
|
|
|
+ questionType: this.filter.questionType || undefined,
|
|
|
+ pageNum: this.filter.pageNum,
|
|
|
+ size: this.filter.size
|
|
|
+ });
|
|
|
+ this.list = data.list || [];
|
|
|
+ this.total = data.count || 0;
|
|
|
+ if (data.questionBankName && !this.questionBankName) {
|
|
|
+ this.questionBankName = data.questionBankName;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ this.$message.error(error.message || '查询试题失败');
|
|
|
+ this.list = [];
|
|
|
+ this.total = 0;
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ handleSearch() {
|
|
|
+ this.filter.pageNum = 1;
|
|
|
+ this.loadList();
|
|
|
+ },
|
|
|
+ handleReset() {
|
|
|
+ this.filter.questionContent = '';
|
|
|
+ this.filter.questionType = '';
|
|
|
+ this.filter.questionBankName = this.questionBankName;
|
|
|
+ this.filter.pageNum = 1;
|
|
|
+ this.loadList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+ .question-list-page {
|
|
|
+ .filter-bar {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ padding-bottom: 16px;
|
|
|
+ border-bottom: 1px solid #ebeef5;
|
|
|
+ }
|
|
|
+ .page-meta {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ color: #606266;
|
|
|
+ font-size: 14px;
|
|
|
+ display: flex;
|
|
|
+ gap: 24px;
|
|
|
+ }
|
|
|
+ .question-list {
|
|
|
+ min-height: 200px;
|
|
|
+ }
|
|
|
+ .question-item {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ ::v-deep .el-card__body {
|
|
|
+ padding: 16px 20px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .question-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ .question-no {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+ .question-score {
|
|
|
+ color: #909399;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .question-title,
|
|
|
+ .question-options,
|
|
|
+ .question-answer,
|
|
|
+ .question-explanation {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ line-height: 1.7;
|
|
|
+ color: #303133;
|
|
|
+ .label {
|
|
|
+ color: #909399;
|
|
|
+ margin-right: 6px;
|
|
|
+ }
|
|
|
+ .content {
|
|
|
+ color: #303133;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .question-options {
|
|
|
+ display: flex;
|
|
|
+ .options {
|
|
|
+ flex: 1;
|
|
|
+ .option-row {
|
|
|
+ padding: 2px 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ .option-key {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ min-width: 22px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .question-image {
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ margin-left: 8px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ vertical-align: middle;
|
|
|
+ ::v-deep img {
|
|
|
+ width: 60px;
|
|
|
+ height: 60px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .option-image {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ ::v-deep img {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .answer-text {
|
|
|
+ color: #67c23a;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ .less-score {
|
|
|
+ margin-left: 12px;
|
|
|
+ color: #e6a23c;
|
|
|
+ font-size: 13px;
|
|
|
+ }
|
|
|
+ .pagination-wrapper {
|
|
|
+ margin-top: 16px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|