【修改】修改试卷页面,其它细节
This commit is contained in:
@@ -5,10 +5,16 @@ export interface QuestionQueryVO {
|
||||
subjectName: string
|
||||
}
|
||||
|
||||
// 查询用户管理列表
|
||||
// 查询试题列表
|
||||
export const getQuestionList = (params: QuestionQueryVO) => {
|
||||
return request.get({ url: '/exam/question/list', params })
|
||||
}
|
||||
// 查询试题列表(带答案)
|
||||
export const getQuestionlistAnswer = (params: QuestionQueryVO) => {
|
||||
return request.get({ url: '/exam/question/listAnswer', params })
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const getQuestionSendList = (params: QuestionQueryVO) => {
|
||||
return request.get({ url: '/exam/question/sendList', params })
|
||||
@@ -25,6 +31,12 @@ export async function auditQuestion(data) {
|
||||
export async function rabbitmqConnect() {
|
||||
return await request.post({ url: '/rabbitmq/connect' })
|
||||
}
|
||||
export async function changePaperQu(data) {
|
||||
return await request.post({ url: '/exam/qu/changePaperQu',data })
|
||||
}
|
||||
export async function changePaperQuRandom(data) {
|
||||
return await request.post({ url: '/exam/qu/changePaperQuRandom',data })
|
||||
}
|
||||
|
||||
// 获取试题详情
|
||||
export const getQuestion = (id: number) => {
|
||||
@@ -63,7 +75,13 @@ export function editQuestionNoAudit(data: any) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 更改状态
|
||||
export async function updateQuStatus(quId: string, status: number) {
|
||||
return await request.put({
|
||||
url: '/exam/question/updateQuStatus',
|
||||
data: { quId, status } // 使用data
|
||||
})
|
||||
}
|
||||
|
||||
export const getQuestionExamineList = (params: QuestionQueryVO) => {
|
||||
return request.get({ url: '/exam/question/auditList', params })
|
||||
|
||||
@@ -72,7 +72,9 @@ export async function getPaperDetailByTaskId(paperId) {
|
||||
return await request.get({ url: '/exam/qu/getPaper' , params:{paperId} })
|
||||
}
|
||||
|
||||
|
||||
export async function getPaperDetailCenterByTaskId(paperId) {
|
||||
return await request.get({ url: '/exam/qu/getPaperCenter' , params:{paperId} })
|
||||
}
|
||||
export async function getPaperList(taskId) {
|
||||
|
||||
return await request.get({ url: '/exam/paper/getPaperByTaskId' , params:{taskId} })
|
||||
|
||||
971
src/views/paper/audit/BrowerForm.vue
Normal file
971
src/views/paper/audit/BrowerForm.vue
Normal file
@@ -0,0 +1,971 @@
|
||||
<template>
|
||||
<div class="edit-dialog">
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="85%" top="10vh" class="custom-dialog">
|
||||
<el-scrollbar>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="specialtyName">
|
||||
<el-input v-model="formData.specialtyName" placeholder="请输入专业" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="知识点" prop="pointNames">
|
||||
<el-input v-model="formData.pointNames" placeholder="请输入知识点" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课程" prop="courseName">
|
||||
<el-input v-model="formData.courseName" placeholder="请输入课程" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- <el-form-item label="题型难度" prop="quLevel">
|
||||
<el-input v-model="formData.quLevel" placeholder="请输入题型难度" />
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="题型难度" prop="quLevel">
|
||||
<el-select
|
||||
v-model="formData.quLevel"
|
||||
placeholder="请选择题型难度"
|
||||
clearable
|
||||
>
|
||||
<el-option label="简单" :value="'0'" />
|
||||
<el-option label="一般" :value="'1'" />
|
||||
<el-option label="困难" :value="'2'" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="题型" prop="subjectName">
|
||||
<el-input v-model="formData.subjectName" placeholder="请输入题型" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="章节名称" prop="chapteridDictText">
|
||||
<el-input v-model="formData.chapteridDictText" placeholder="请输入章节名称" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :label="'0'">启用</el-radio>
|
||||
<el-radio :label="'1'">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
|
||||
</el-form>
|
||||
<div class="edit-bottom">
|
||||
<div class="edit-left bottom-common">
|
||||
<el-tabs v-model="leftActiveName" class="demo-tabs">
|
||||
<el-tab-pane label="试题描述" name="desc">
|
||||
<div class="block">
|
||||
<Editor v-model="formData.content" height="250px" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div class="edit-right bottom-common">
|
||||
<el-tabs v-model="rightActiveName" class="demo-tabs" @tab-click="rightHandleClick">
|
||||
|
||||
<el-tab-pane label="试题解析" name="analysis">
|
||||
<div class="block">
|
||||
<Editor v-model="formData.analysis" height="250px" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="keyword">
|
||||
<template #label>
|
||||
<div class="custom-tabs-label">
|
||||
<p>关键字</p>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link" @click.stop="false">
|
||||
<div class="setting_icon"></div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="editKeyword('create')">新建</el-dropdown-item>
|
||||
<el-dropdown-item @click="editKeyword('update')">编辑</el-dropdown-item>
|
||||
<el-dropdown-item @click="editKeyword('delete')">删除</el-dropdown-item>
|
||||
<el-dropdown-item @click="editKeyword('deleteall')"
|
||||
>删除全部</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<div class="block">
|
||||
<el-table
|
||||
:data="keywordList"
|
||||
style="width: 100%"
|
||||
@selection-change="handleKeywordSelectionChange"
|
||||
>
|
||||
<el-table-column type="index" width="50" />
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="keyword" label="关键字" />
|
||||
</el-table>
|
||||
<el-dialog
|
||||
v-model="keyVisible"
|
||||
title="编辑关键字"
|
||||
width="50%"
|
||||
:before-close="keyDialogClose"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div class="main" style="width: 100%; height: 100%">
|
||||
<el-input v-model="keyWord" placeholder="请输入关键字" size="large" />
|
||||
<div class="dialog-footer" style="margin-top: 20px;">
|
||||
<el-button @click="keyDialogClose">取消</el-button>
|
||||
<el-button type="primary" @click="confirmKeyDialogVisible">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane name="medium">
|
||||
<template #label>
|
||||
<div class="custom-tabs-label">
|
||||
<p>媒体文件</p>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link" @click.stop="false">
|
||||
<div class="setting_icon"></div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="upload-demo"
|
||||
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
||||
>
|
||||
<el-button>导入</el-button>
|
||||
</el-upload>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>导出</el-dropdown-item>
|
||||
<el-dropdown-item>播放</el-dropdown-item>
|
||||
<el-dropdown-item>编辑</el-dropdown-item>
|
||||
<el-dropdown-item>删除选中</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<div class="block">
|
||||
<el-table
|
||||
:data="mediumList"
|
||||
style="width: 100%"
|
||||
@selection-change="handleMediumSelectionChange"
|
||||
>
|
||||
<el-table-column type="index" width="50" />
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="type" label="媒体类型" width="80" />
|
||||
<el-table-column prop="title" label="标题" />
|
||||
<el-table-column prop="displayIndex" label="显示序号" />
|
||||
<el-table-column prop="size" label="大小" />
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane> -->
|
||||
|
||||
<el-tab-pane v-if="formType === 'update'" name="point">
|
||||
|
||||
<template #label>
|
||||
<div class="custom-tabs-label">
|
||||
<p>考点设置</p>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link" @click.stop="false">
|
||||
<div class="setting_icon"></div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="setKao()">考点设置</el-dropdown-item>
|
||||
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="block" style="height: 300px;width: 100% ;">
|
||||
<el-table
|
||||
:data="kaodianList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" />
|
||||
<el-table-column prop="scoreRate" label="权值" width="90" />
|
||||
<el-table-column prop="content" label="文件名(不带后缀)" width="260" />
|
||||
<el-table-column prop="contentIn" label="考察类型" width="300" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 弹框 -->
|
||||
<el-dialog
|
||||
title="考点设置"
|
||||
v-model="kaoDialogVisible"
|
||||
width="60%"
|
||||
:close-on-click-modal="false"
|
||||
class="custom-dialog"
|
||||
>
|
||||
<!-- 可滚动容器 -->
|
||||
<div style="height: 400px; overflow-y: auto;">
|
||||
<el-table
|
||||
:data="kaodianList"
|
||||
style="width: 100%;"
|
||||
row-key="answerId"
|
||||
:default-expand-all="false"
|
||||
|
||||
|
||||
>
|
||||
|
||||
<el-table-column type="index" label="序号" width="60" />
|
||||
|
||||
<el-table-column label="文件名(不带后缀)" width="260">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.content"
|
||||
size="small"
|
||||
type="input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="权值" width="100">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.scoreRate"
|
||||
size="small"
|
||||
type="number"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="考察类型" width="300">
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.contentIn"
|
||||
placeholder="请选择类型"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="添加到收藏夹" value="添加到收藏夹" />
|
||||
<el-option label="添加到文件夹" value="添加到文件夹" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeKaodian(scope.$index)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
|
||||
<!-- 增加按钮 -->
|
||||
<div style="margin-top: 12px; text-align: center;">
|
||||
|
||||
<el-button type="primary" @click="addKaodianRow">
|
||||
添加考点
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<template #footer>
|
||||
|
||||
|
||||
<el-button @click="kaoDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmKao">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
|
||||
|
||||
</el-tab-pane>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<el-tab-pane name="annex">
|
||||
<template #label>
|
||||
<div class="custom-tabs-label">
|
||||
<p>试题附件</p>
|
||||
<!-- <el-dropdown>
|
||||
<span class="el-dropdown-link" @click.stop="false">
|
||||
<div class="setting_icon"></div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>导入单文件</el-dropdown-item>
|
||||
<el-dropdown-item>导入多文件</el-dropdown-item>
|
||||
<el-dropdown-item>导出</el-dropdown-item>
|
||||
<el-dropdown-item>删除</el-dropdown-item>
|
||||
<el-dropdown-item>帮助</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown> -->
|
||||
</div>
|
||||
</template>
|
||||
<!-- 提示 -->
|
||||
<el-alert type="warning" show-icon :closable="false">
|
||||
<template #default>
|
||||
<span>提示:文件名称可默认不设置</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div class="block">
|
||||
<el-table :data="formData.fileUploads" style="width: 100%">
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="文件名称" width="250">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.fileName"
|
||||
size="small"
|
||||
placeholder="请输入文件名称"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="fileType"
|
||||
label="类型"
|
||||
:formatter="fileTypeFormatter"
|
||||
/>
|
||||
<el-table-column prop="url" label="地址" width="200">
|
||||
<template #default="{ row }">
|
||||
<div
|
||||
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"
|
||||
:title="row.url"
|
||||
>
|
||||
{{ row.url }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="320">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm(scope.row.fileType)"
|
||||
size="small"
|
||||
>
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 上传
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="downloadFile(scope.row.url)"
|
||||
size="small"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 下载
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
@click="deleteUrl(scope.$index)"
|
||||
size="small"
|
||||
><Icon icon="ep:download" class="mr-5px" />删除
|
||||
</el-button>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<FileForm ref="FileRef" @success="handleUploadSuccess"/>
|
||||
|
||||
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { FormRules } from 'element-plus'
|
||||
import * as QuestionApi from '@/api/paper/question'
|
||||
import FileForm from './components/FileForm.vue';
|
||||
defineOptions({ name: 'ChoiceForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
//右键菜单状态
|
||||
const kaoDialogVisible = ref(false)
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
content: '',
|
||||
specialtyName: '',
|
||||
courseName: '',
|
||||
quBankName: '',
|
||||
required: '',
|
||||
chapteridDictText: '',
|
||||
analysis: '',
|
||||
quLevel: '',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: ' ',
|
||||
keywords: '',
|
||||
resourceValue: '',
|
||||
fileUploads: [ {
|
||||
quId: '',
|
||||
url: '',
|
||||
fileType: '1',
|
||||
fileName: ''
|
||||
},
|
||||
{
|
||||
quId: '',
|
||||
url: '',
|
||||
fileType: '2',
|
||||
fileName: ''
|
||||
}]
|
||||
})
|
||||
const kaodianList = ref<any[]>([])
|
||||
function fileTypeFormatter(row, column, cellValue) {
|
||||
if (cellValue === '1') return '考试文件'
|
||||
return '未知类型'
|
||||
}
|
||||
|
||||
//考点
|
||||
|
||||
// formData.value.quId 中包含 quId,假设它已有值
|
||||
const kaodianData = ref({
|
||||
quId: '', // 你打开编辑弹窗时会赋值
|
||||
})
|
||||
function setKao() {
|
||||
kaoDialogVisible.value = true
|
||||
}
|
||||
|
||||
function confirmKao() {
|
||||
if (!kaodianList.value.length) {
|
||||
ElMessage.warning("没有考点数据");
|
||||
return;
|
||||
}
|
||||
// 构建 questionAnswerList
|
||||
const questionAnswerList = kaodianList.value.map(item => ({
|
||||
answerId: item.answerId,
|
||||
content: item.content,
|
||||
quId:kaodianData.value.quId,
|
||||
contentIn:item.contentIn,
|
||||
scoreRate:item.scoreRate
|
||||
}));
|
||||
const payload = {
|
||||
quId:kaodianData.value.quId,
|
||||
questionAnswerList
|
||||
};
|
||||
|
||||
|
||||
console.log('确认后的结果:', payload)
|
||||
|
||||
QuestionApi.setBrowserPoint(payload)
|
||||
kaoDialogVisible.value= false;
|
||||
}
|
||||
|
||||
// 删除行
|
||||
function removeKaodian(index: number) {
|
||||
kaodianList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
// 增加行
|
||||
function addKaodianRow() {
|
||||
kaodianList.value.push({
|
||||
answerId: '', // 用你项目里生成 ID 的方法
|
||||
content: '',
|
||||
quId:kaodianData.value.quId,
|
||||
scoreRate: 1,
|
||||
contentIn: ''
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const formRules = reactive<FormRules>({
|
||||
// specialtyName: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
// 左侧试题描述
|
||||
const leftActiveName = ref('desc')
|
||||
// 右侧tab
|
||||
const rightActiveName = ref('analysis')
|
||||
const rightHandleClick = (tab, e) => {
|
||||
rightActiveName.value = tab.paneName.value
|
||||
|
||||
if (tab.paneName === 'point' ) {
|
||||
// 发起考点请求
|
||||
QuestionApi.getListByQuId(kaodianData.value.quId).then(res => {
|
||||
// res 是接口返回的数据数组
|
||||
kaodianList.value = res;
|
||||
});
|
||||
}
|
||||
}
|
||||
const radio = ref('A')
|
||||
// 保留选项的值
|
||||
const optionsContent = reactive({})
|
||||
|
||||
// 关键字
|
||||
const keywordList = ref([] as any)
|
||||
const multipleKeywordSelection = ref([] as any)
|
||||
const handleKeywordSelectionChange = (val: any) => {
|
||||
multipleKeywordSelection.value = val
|
||||
}
|
||||
const keyVisible = ref(false)
|
||||
const keyEditType = ref('')
|
||||
|
||||
const keyWord = ref('')
|
||||
|
||||
|
||||
const editKeyword = (key: string) => {
|
||||
keyEditType.value = key
|
||||
|
||||
if (key === 'create') {
|
||||
keyWord.value = ''
|
||||
keyVisible.value = true
|
||||
} else if (key === 'update') {
|
||||
if (multipleKeywordSelection.value.length === 0) {
|
||||
ElMessage.warning('请先选择一个要编辑的关键字')
|
||||
return
|
||||
}
|
||||
keyWord.value = multipleKeywordSelection.value[0].keyword
|
||||
keyVisible.value = true
|
||||
} else if (key === 'delete') {
|
||||
if (multipleKeywordSelection.value.length === 0) {
|
||||
ElMessage.warning('请先选择要删除的关键字')
|
||||
return
|
||||
}
|
||||
keywordList.value = keywordList.value.filter(
|
||||
item => !multipleKeywordSelection.value.includes(item)
|
||||
)
|
||||
ElMessage.success('已删除选中项')
|
||||
} else if (key === 'deleteall') {
|
||||
keywordList.value = []
|
||||
ElMessage.success('已清空关键字列表')
|
||||
}
|
||||
updateKeywordsToForm()
|
||||
}
|
||||
const updateKeywordsToForm = () => {
|
||||
const keywordStr = keywordList.value
|
||||
.map(item => item.keyword)
|
||||
.filter(k => k && k.trim() !== '')
|
||||
.join(',')
|
||||
formData.value.keywords = keywordStr
|
||||
|
||||
console.log(formData.value.keywords+"formData.value.keywords")
|
||||
}
|
||||
|
||||
const keyDialogClose = () => {
|
||||
keyVisible.value = false
|
||||
}
|
||||
const confirmKeyDialogVisible = () => {
|
||||
if (keyEditType.value === 'create') {
|
||||
keywordList.value.push({
|
||||
keyword: keyWord.value
|
||||
})
|
||||
} else if (keyEditType.value === 'update') {
|
||||
multipleKeywordSelection.value.forEach(item => {
|
||||
item.keyword = keyWord.value
|
||||
})
|
||||
} else if (keyEditType.value === 'delete') {
|
||||
keywordList.value = keywordList.value.filter(
|
||||
item => !multipleKeywordSelection.value.includes(item)
|
||||
)
|
||||
} else if (keyEditType.value === 'deleteall') {
|
||||
keywordList.value = []
|
||||
}
|
||||
|
||||
updateKeywordsToForm()
|
||||
keyVisible.value = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const FileRef = ref()
|
||||
const openForm = (type: string) => {
|
||||
FileRef.value.open(type)
|
||||
}
|
||||
|
||||
// 媒体文件
|
||||
const mediumList = ref([] as any)
|
||||
const multipleMediumSelection = ref([] as any)
|
||||
const handleMediumSelectionChange = (val: any) => {
|
||||
multipleMediumSelection.value = val
|
||||
}
|
||||
const fileList = []
|
||||
|
||||
|
||||
const multipleDocumentSelection = ref([] as any)
|
||||
const handleDocumentSelectionChange = (val: any) => {
|
||||
multipleDocumentSelection.value = val
|
||||
}
|
||||
//文件
|
||||
const handleUploadSuccess = ({ url, fileType }) => {
|
||||
const index = formData.value.fileUploads.findIndex(item => item.fileType === fileType)
|
||||
if (index !== -1) {
|
||||
formData.value.fileUploads[index].url = url
|
||||
}
|
||||
}
|
||||
|
||||
const downloadFile = async (url: string) => {
|
||||
if (!url) {
|
||||
ElMessage.warning('暂无可下载的文件地址')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error('下载失败')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const blobUrl = window.URL.createObjectURL(blob)
|
||||
|
||||
// 提取文件名
|
||||
const filename = url.substring(url.lastIndexOf('/') + 1).split('?')[0]
|
||||
|
||||
// 创建 a 标签并下载
|
||||
const a = document.createElement('a')
|
||||
a.href = blobUrl
|
||||
a.download = filename
|
||||
a.style.display = 'none'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
a.remove()
|
||||
|
||||
window.URL.revokeObjectURL(blobUrl)
|
||||
} catch (err: any) {
|
||||
ElMessage.error(`下载失败:${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const deleteUrl = (index: number) => {
|
||||
formData.value.fileUploads[index].url = ''
|
||||
formData.value.fileUploads[index].fileName = ''
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (queryParams: any ,type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
rightActiveName.value = 'analysis'
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
kaodianData.value.quId=id;
|
||||
formLoading.value = true
|
||||
try {
|
||||
const res = await QuestionApi.getQuestionnotId(id);
|
||||
|
||||
// 默认两个类型
|
||||
const fileTypes = ['1'];
|
||||
|
||||
// 后端返回的上传文件列表(可能为空)
|
||||
const documentList = res.fileUploads ?? [];
|
||||
|
||||
// 遍历两种类型,找到对应的上传文件,如果没有就用默认值
|
||||
const fileUploads= fileTypes.map(type => {
|
||||
const match = documentList.find(file => file.fileType === type);
|
||||
return {
|
||||
quId: match?.quId ?? res.quId ?? '',
|
||||
url: match?.url ?? '',
|
||||
fileType: type,
|
||||
fileName: match?.fileName ?? ''
|
||||
};
|
||||
});
|
||||
|
||||
formData.value = {
|
||||
...res,
|
||||
fileUploads,
|
||||
};
|
||||
keywordList.value = res.keywords
|
||||
? res.keywords.split(',').filter(item => item.trim() !== '').map(item => ({ keyword: item.trim() }))
|
||||
: []
|
||||
} catch (error) {
|
||||
console.error("获取问题失败:", error);
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
} else {
|
||||
resetForm()
|
||||
formData.value.specialtyName = queryParams.specialtyName
|
||||
formData.value.courseName = queryParams.courseName
|
||||
formData.value.subjectName = queryParams.subjectName
|
||||
formData.value.pointNames=queryParams.pointNames
|
||||
formData.value.chapteridDictText=queryParams.chapteridDictText
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
// 转换函数:将大写字母 A-Z 映射为 0-25
|
||||
const mappedNumber = computed(() => {
|
||||
const char = radio.value.toUpperCase();
|
||||
const code = char.charCodeAt(0);
|
||||
if (code >= 65 && code <= 90) {
|
||||
return code - 65;
|
||||
} else {
|
||||
return '请输入 A-Z 的字母';
|
||||
}
|
||||
});
|
||||
// 选择答案数据
|
||||
const answerData = ref([]);
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return;
|
||||
const valid = await formRef.value.validate();
|
||||
if (!valid) return;
|
||||
|
||||
formLoading.value = true;
|
||||
try {
|
||||
// 深拷贝一份 formData
|
||||
const data = JSON.parse(JSON.stringify(formData.value));
|
||||
|
||||
// 过滤掉 url 为空的文件项
|
||||
data.fileUploads = data.fileUploads?.filter(file => file.url && file.url.trim() !== '');
|
||||
|
||||
|
||||
await QuestionApi.editQuestionNoAudit(data);
|
||||
message.success(t('common.updateSuccess'));
|
||||
|
||||
|
||||
dialogVisible.value = false;
|
||||
emit('success');
|
||||
} finally {
|
||||
formLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
content: `---------------------------------------------------------------------
|
||||
请在打开的窗口中,进行下列操作,完成所有操作后,请关闭窗口。
|
||||
---------------------------------------------------------------------
|
||||
`,
|
||||
specialtyName: '',
|
||||
courseName: '',
|
||||
quBankName: '',
|
||||
required: '',
|
||||
chapteridDictText: '',
|
||||
analysis: '',
|
||||
quLevel: '0',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: '0',
|
||||
keywords: '',
|
||||
resourceValue: '',
|
||||
|
||||
fileUploads: [ {
|
||||
quId: '',
|
||||
url: '',
|
||||
fileType: '1',
|
||||
fileName: ''
|
||||
},
|
||||
{
|
||||
quId: '',
|
||||
url: '',
|
||||
fileType: '2',
|
||||
fileName: ''
|
||||
}]
|
||||
}
|
||||
keywordList.value=[],
|
||||
formRef.value?.resetFields()
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.edit-dialog {
|
||||
:deep(.el-dialog) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
border-top: 1px solid #ededed;
|
||||
}
|
||||
.el-dialog__body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
.main {
|
||||
.el-form {
|
||||
padding: 10px;
|
||||
.el-row {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.el-form-item {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.edit-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 10px;
|
||||
.bottom-common {
|
||||
width: calc(50% - 10px);
|
||||
.el-tabs {
|
||||
height: 100%;
|
||||
.el-tabs__content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
.block {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
overflow: auto;
|
||||
}
|
||||
.answer {
|
||||
.tip {
|
||||
color: #8a6d3b;
|
||||
background-color: #fcf8e3;
|
||||
border-color: #faebcc;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> p {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.el-radio-group {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
font-size: 0;
|
||||
flex-direction: column;
|
||||
.options {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
.content {
|
||||
display: flex;
|
||||
.text {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
border: 1px solid #ededed;
|
||||
.el-textarea__inner {
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.more-btn {
|
||||
margin-top: 8px;
|
||||
background-color: #ffffff;
|
||||
border-color: #007bff;
|
||||
color: #007bff;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 10px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-tabs-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.setting_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url('@/assets/icon/setting_blue.png') no-repeat center;
|
||||
background-size: 100%;
|
||||
margin-left: 3px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
.custom-tabs-label {
|
||||
.setting_icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ele-pro-table) {
|
||||
flex: 1;
|
||||
margin-top: 10px;
|
||||
.el-table--fit {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.tox-tinymce) {
|
||||
.tox-statusbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
.el-table__header-wrapper {
|
||||
.el-table__header {
|
||||
thead {
|
||||
tr {
|
||||
th {
|
||||
background: #ebebeb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -349,9 +349,12 @@ const open = async (queryParams: any ,type: string, id?: number) => {
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
resetForm()
|
||||
console.log(id+"idid")
|
||||
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
|
||||
|
||||
formLoading.value = true
|
||||
try {
|
||||
formData.value = await QuestionApi.getQuestionnotId(id)
|
||||
@@ -419,13 +422,10 @@ const submitForm = async () => {
|
||||
formLoading.value = true
|
||||
try {
|
||||
const data = formData.value as unknown
|
||||
if (formType.value === 'create') {
|
||||
await QuestionApi.addQuestion(data)
|
||||
message.success(t('common.createSuccess'))
|
||||
} else {
|
||||
|
||||
await QuestionApi.editQuestionNoAudit(data)
|
||||
message.success(t('common.updateSuccess'))
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
// 发送操作成功的事件
|
||||
emit('success')
|
||||
@@ -449,8 +449,14 @@ const resetForm = () => {
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: '',
|
||||
resourceValue: ''
|
||||
resourceValue: '',
|
||||
|
||||
}
|
||||
radio.value = 'A',
|
||||
// 清空 optionsContent 的属性
|
||||
Object.keys(optionsContent).forEach(key => {
|
||||
delete optionsContent[key]
|
||||
})
|
||||
formRef.value?.resetFields()
|
||||
}
|
||||
</script>
|
||||
|
||||
993
src/views/paper/audit/FileForm.vue
Normal file
993
src/views/paper/audit/FileForm.vue
Normal file
@@ -0,0 +1,993 @@
|
||||
<template>
|
||||
<div class="edit-dialog">
|
||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="85%" top="10vh" class="custom-dialog">
|
||||
<el-scrollbar>
|
||||
<div class="main">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="专业" prop="specialtyName">
|
||||
<el-input v-model="formData.specialtyName" placeholder="请输入专业" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="知识点" prop="pointNames">
|
||||
<el-input v-model="formData.pointNames" placeholder="请输入知识点" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课程" prop="courseName">
|
||||
<el-input v-model="formData.courseName" placeholder="请输入课程" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- <el-form-item label="题型难度" prop="quLevel">
|
||||
<el-input v-model="formData.quLevel" placeholder="请输入题型难度" />
|
||||
</el-form-item> -->
|
||||
|
||||
<el-form-item label="题型难度" prop="quLevel">
|
||||
<el-select
|
||||
v-model="formData.quLevel"
|
||||
placeholder="请选择题型难度"
|
||||
clearable
|
||||
>
|
||||
<el-option label="简单" :value="'0'" />
|
||||
<el-option label="一般" :value="'1'" />
|
||||
<el-option label="困难" :value="'2'" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="题型" prop="subjectName">
|
||||
<el-input v-model="formData.subjectName" placeholder="请输入题型" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="章节名称" prop="chapteridDictText">
|
||||
<el-input v-model="formData.chapteridDictText" placeholder="请输入章节名称" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :label="'0'">启用</el-radio>
|
||||
<el-radio :label="'1'">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
|
||||
</el-form>
|
||||
<div class="edit-bottom">
|
||||
<div class="edit-left bottom-common">
|
||||
<el-tabs v-model="leftActiveName" class="demo-tabs">
|
||||
<el-tab-pane label="试题描述" name="desc">
|
||||
<div class="block">
|
||||
<Editor v-model="formData.content" height="250px" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
<div class="edit-right bottom-common">
|
||||
<el-tabs v-model="rightActiveName" class="demo-tabs" @tab-click="rightHandleClick">
|
||||
|
||||
<el-tab-pane label="试题解析" name="analysis">
|
||||
<div class="block">
|
||||
<Editor v-model="formData.analysis" height="250px" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="keyword">
|
||||
<template #label>
|
||||
<div class="custom-tabs-label">
|
||||
<p>关键字</p>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link" @click.stop="false">
|
||||
<div class="setting_icon"></div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="editKeyword('create')">新建</el-dropdown-item>
|
||||
<el-dropdown-item @click="editKeyword('update')">编辑</el-dropdown-item>
|
||||
<el-dropdown-item @click="editKeyword('delete')">删除</el-dropdown-item>
|
||||
<el-dropdown-item @click="editKeyword('deleteall')"
|
||||
>删除全部</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<div class="block">
|
||||
<el-table
|
||||
:data="keywordList"
|
||||
style="width: 100%"
|
||||
@selection-change="handleKeywordSelectionChange"
|
||||
>
|
||||
<el-table-column type="index" width="50" />
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="keyword" label="关键字" />
|
||||
</el-table>
|
||||
<el-dialog
|
||||
v-model="keyVisible"
|
||||
title="编辑关键字"
|
||||
width="50%"
|
||||
:before-close="keyDialogClose"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<div class="main" style="width: 100%; height: 100%">
|
||||
<el-input v-model="keyWord" placeholder="请输入关键字" size="large" />
|
||||
<div class="dialog-footer" style="margin-top: 20px;">
|
||||
<el-button @click="keyDialogClose">取消</el-button>
|
||||
<el-button type="primary" @click="confirmKeyDialogVisible">
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<!-- <el-tab-pane name="medium">
|
||||
<template #label>
|
||||
<div class="custom-tabs-label">
|
||||
<p>媒体文件</p>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link" @click.stop="false">
|
||||
<div class="setting_icon"></div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<el-upload
|
||||
v-model:file-list="fileList"
|
||||
class="upload-demo"
|
||||
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
||||
>
|
||||
<el-button>导入</el-button>
|
||||
</el-upload>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item>导出</el-dropdown-item>
|
||||
<el-dropdown-item>播放</el-dropdown-item>
|
||||
<el-dropdown-item>编辑</el-dropdown-item>
|
||||
<el-dropdown-item>删除选中</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<div class="block">
|
||||
<el-table
|
||||
:data="mediumList"
|
||||
style="width: 100%"
|
||||
@selection-change="handleMediumSelectionChange"
|
||||
>
|
||||
<el-table-column type="index" width="50" />
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="type" label="媒体类型" width="80" />
|
||||
<el-table-column prop="title" label="标题" />
|
||||
<el-table-column prop="displayIndex" label="显示序号" />
|
||||
<el-table-column prop="size" label="大小" />
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane> -->
|
||||
|
||||
<el-tab-pane v-if="formType === 'update'" name="point">
|
||||
<el-alert type="warning" show-icon :closable="false">
|
||||
<template #default>
|
||||
<span>提示:考点导入需上传考试和结果文件</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<template #label>
|
||||
<div class="custom-tabs-label">
|
||||
<p>考点设置</p>
|
||||
<el-dropdown>
|
||||
<span class="el-dropdown-link" @click.stop="false">
|
||||
<div class="setting_icon"></div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="setKao()">考点设置</el-dropdown-item>
|
||||
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="block" style="height: 300px;width: 100% ;">
|
||||
<el-table
|
||||
:data="kaodianList"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" />
|
||||
<el-table-column prop="scoreRate" label="权值" width="90" />
|
||||
<el-table-column prop="content" label="文件/文件夹名(带后缀)" width="260" />
|
||||
<el-table-column prop="contentIn" label="考察类型" width="300" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 弹框 -->
|
||||
<el-dialog
|
||||
title="考点设置"
|
||||
v-model="kaoDialogVisible"
|
||||
width="60%"
|
||||
:close-on-click-modal="false"
|
||||
class="custom-dialog"
|
||||
>
|
||||
<!-- 可滚动容器 -->
|
||||
<div style="height: 400px; overflow-y: auto;">
|
||||
<el-table
|
||||
:data="kaodianList"
|
||||
style="width: 100%;"
|
||||
row-key="answerId"
|
||||
:default-expand-all="false"
|
||||
|
||||
|
||||
>
|
||||
|
||||
<el-table-column type="index" label="序号" width="60" />
|
||||
|
||||
<el-table-column label="文件/文件夹名(带后缀)" width="260">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.content"
|
||||
size="small"
|
||||
type="input"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="权值" width="100">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.scoreRate"
|
||||
size="small"
|
||||
type="number"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="考察类型" width="300">
|
||||
<template #default="scope">
|
||||
<el-select
|
||||
v-model="scope.row.contentIn"
|
||||
placeholder="请选择类型"
|
||||
size="small"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="考察名称" value="考察名称" />
|
||||
<el-option label="考察删除" value="考察删除" />
|
||||
<el-option label="考察属性" value="考察属性" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="removeKaodian(scope.$index)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
<!-- 增加按钮 -->
|
||||
<div style="margin-top: 12px; text-align: center;">
|
||||
<el-button type="primary" @click="setKaodianRow">
|
||||
导入考点
|
||||
</el-button>
|
||||
<el-button type="primary" @click="addKaodianRow">
|
||||
添加考点
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<template #footer>
|
||||
|
||||
|
||||
<el-button @click="kaoDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmKao">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
|
||||
</el-tab-pane>
|
||||
<el-tab-pane name="annex">
|
||||
<template #label>
|
||||
<div class="custom-tabs-label">
|
||||
<p>试题附件</p>
|
||||
<!-- <el-dropdown>
|
||||
<span class="el-dropdown-link" @click.stop="false">
|
||||
<div class="setting_icon"></div>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>导入单文件</el-dropdown-item>
|
||||
<el-dropdown-item>导入多文件</el-dropdown-item>
|
||||
<el-dropdown-item>导出</el-dropdown-item>
|
||||
<el-dropdown-item>删除</el-dropdown-item>
|
||||
<el-dropdown-item>帮助</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown> -->
|
||||
</div>
|
||||
</template>
|
||||
<!-- 提示 -->
|
||||
<el-alert type="warning" show-icon :closable="false">
|
||||
<template #default>
|
||||
<span>提示:文件名称可默认不设置</span>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div class="block">
|
||||
<el-table :data="formData.fileUploads" style="width: 100%">
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column label="文件名称" width="250">
|
||||
<template #default="scope">
|
||||
<el-input
|
||||
v-model="scope.row.fileName"
|
||||
size="small"
|
||||
placeholder="请输入文件名称"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="fileType"
|
||||
label="类型"
|
||||
:formatter="fileTypeFormatter"
|
||||
/>
|
||||
<el-table-column prop="url" label="地址" width="200">
|
||||
<template #default="{ row }">
|
||||
<div
|
||||
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"
|
||||
:title="row.url"
|
||||
>
|
||||
{{ row.url }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="320">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm(scope.row.fileType)"
|
||||
size="small"
|
||||
>
|
||||
<Icon icon="ep:upload" class="mr-5px" /> 上传
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
@click="downloadFile(scope.row.url)"
|
||||
size="small"
|
||||
>
|
||||
<Icon icon="ep:download" class="mr-5px" /> 下载
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
plain
|
||||
@click="deleteUrl(scope.$index)"
|
||||
size="small"
|
||||
><Icon icon="ep:download" class="mr-5px" />删除
|
||||
</el-button>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</div>
|
||||
<!-- 表单弹窗:添加/修改 -->
|
||||
<FileForm ref="FileRef" @success="handleUploadSuccess"/>
|
||||
|
||||
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import { FormRules } from 'element-plus'
|
||||
import * as QuestionApi from '@/api/paper/question'
|
||||
import FileForm from './components/FileForm.vue';
|
||||
defineOptions({ name: 'ChoiceForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
content: '',
|
||||
specialtyName: '',
|
||||
courseName: '',
|
||||
quBankName: '',
|
||||
required: '',
|
||||
chapteridDictText: '',
|
||||
analysis: '',
|
||||
quLevel: '',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: ' ',
|
||||
keywords: '',
|
||||
resourceValue: '',
|
||||
fileUploads: [ {
|
||||
quId: '',
|
||||
url: '',
|
||||
fileType: '1',
|
||||
fileName: ''
|
||||
},
|
||||
{
|
||||
quId: '',
|
||||
url: '',
|
||||
fileType: '2',
|
||||
fileName: ''
|
||||
}]
|
||||
})
|
||||
|
||||
function fileTypeFormatter(row, column, cellValue) {
|
||||
if (cellValue === '1') return '考试文件'
|
||||
if (cellValue === '2') return '结果文件'
|
||||
return '未知类型'
|
||||
}
|
||||
|
||||
|
||||
//考点
|
||||
const kaoDialogVisible = ref(false)
|
||||
const kaodianData = ref({
|
||||
quId: '', // 你打开编辑弹窗时会赋值
|
||||
})
|
||||
function setKao() {
|
||||
kaoDialogVisible.value = true
|
||||
}
|
||||
const kaodianList = ref<any[]>([])
|
||||
function confirmKao() {
|
||||
if (!kaodianList.value.length) {
|
||||
ElMessage.warning("没有考点数据");
|
||||
return;
|
||||
}
|
||||
// 构建 questionAnswerList
|
||||
const questionAnswerList = kaodianList.value.map(item => ({
|
||||
answerId: item.answerId,
|
||||
content: item.content,
|
||||
quId:kaodianData.value.quId,
|
||||
contentIn:item.contentIn,
|
||||
scoreRate:item.scoreRate
|
||||
}));
|
||||
const payload = {
|
||||
quId:kaodianData.value.quId,
|
||||
questionAnswerList
|
||||
};
|
||||
|
||||
|
||||
console.log('确认后的结果:', payload)
|
||||
|
||||
QuestionApi.setBrowserPoint(payload)
|
||||
kaoDialogVisible.value= false;
|
||||
}
|
||||
|
||||
// 增加行
|
||||
function addKaodianRow() {
|
||||
kaodianList.value.push({
|
||||
answerId: '', // 用你项目里生成 ID 的方法
|
||||
content: '',
|
||||
quId:kaodianData.value.quId,
|
||||
scoreRate: 1,
|
||||
contentIn: ''
|
||||
})
|
||||
}
|
||||
// 删除行
|
||||
function removeKaodian(index: number) {
|
||||
kaodianList.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const setKaodianRow =async () => {
|
||||
const uploads = formData.value.fileUploads;
|
||||
|
||||
if (!uploads[0].url || !uploads[1].url) {
|
||||
ElMessage.error('请先上传两个文件再导入考点');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileUrl1 = uploads[0].url;
|
||||
const fileUrl2 = uploads[1].url;
|
||||
|
||||
console.log('导入考点的文件地址为:', fileUrl1, fileUrl2);
|
||||
const params = {
|
||||
answerPath: fileUrl1,
|
||||
shucaiPath: fileUrl2 // 如果不传可以是空字符串,也可以删除这个字段(根据后端是否必填)
|
||||
};
|
||||
|
||||
const res = await QuestionApi.getFilePoint(params);
|
||||
|
||||
kaodianList.value=res;
|
||||
// 示例:调用后端接口进行导入
|
||||
// importKaodianFromFiles(fileUrl1, fileUrl2);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const formRules = reactive<FormRules>({
|
||||
// specialtyName: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }]
|
||||
})
|
||||
const formRef = ref() // 表单 Ref
|
||||
// 左侧试题描述
|
||||
const leftActiveName = ref('desc')
|
||||
// 右侧tab
|
||||
const rightActiveName = ref('analysis')
|
||||
const rightHandleClick = (tab, e) => {
|
||||
rightActiveName.value = tab.paneName.value
|
||||
if (tab.paneName === 'point' ) {
|
||||
// 发起考点请求
|
||||
QuestionApi.getListByQuId(kaodianData.value.quId).then(res => {
|
||||
// res 是接口返回的数据数组
|
||||
kaodianList.value = res;
|
||||
});
|
||||
}
|
||||
}
|
||||
const radio = ref('A')
|
||||
// 保留选项的值
|
||||
const optionsContent = reactive({})
|
||||
|
||||
// 关键字
|
||||
const keywordList = ref([] as any)
|
||||
const multipleKeywordSelection = ref([] as any)
|
||||
const handleKeywordSelectionChange = (val: any) => {
|
||||
multipleKeywordSelection.value = val
|
||||
}
|
||||
const keyVisible = ref(false)
|
||||
const keyEditType = ref('')
|
||||
|
||||
const keyWord = ref('')
|
||||
|
||||
|
||||
const editKeyword = (key: string) => {
|
||||
keyEditType.value = key
|
||||
|
||||
if (key === 'create') {
|
||||
keyWord.value = ''
|
||||
keyVisible.value = true
|
||||
} else if (key === 'update') {
|
||||
if (multipleKeywordSelection.value.length === 0) {
|
||||
ElMessage.warning('请先选择一个要编辑的关键字')
|
||||
return
|
||||
}
|
||||
keyWord.value = multipleKeywordSelection.value[0].keyword
|
||||
keyVisible.value = true
|
||||
} else if (key === 'delete') {
|
||||
if (multipleKeywordSelection.value.length === 0) {
|
||||
ElMessage.warning('请先选择要删除的关键字')
|
||||
return
|
||||
}
|
||||
keywordList.value = keywordList.value.filter(
|
||||
item => !multipleKeywordSelection.value.includes(item)
|
||||
)
|
||||
ElMessage.success('已删除选中项')
|
||||
} else if (key === 'deleteall') {
|
||||
keywordList.value = []
|
||||
ElMessage.success('已清空关键字列表')
|
||||
}
|
||||
updateKeywordsToForm()
|
||||
}
|
||||
const updateKeywordsToForm = () => {
|
||||
const keywordStr = keywordList.value
|
||||
.map(item => item.keyword)
|
||||
.filter(k => k && k.trim() !== '')
|
||||
.join(',')
|
||||
formData.value.keywords = keywordStr
|
||||
|
||||
console.log(formData.value.keywords+"formData.value.keywords")
|
||||
}
|
||||
|
||||
const keyDialogClose = () => {
|
||||
keyVisible.value = false
|
||||
}
|
||||
const confirmKeyDialogVisible = () => {
|
||||
if (keyEditType.value === 'create') {
|
||||
keywordList.value.push({
|
||||
keyword: keyWord.value
|
||||
})
|
||||
} else if (keyEditType.value === 'update') {
|
||||
multipleKeywordSelection.value.forEach(item => {
|
||||
item.keyword = keyWord.value
|
||||
})
|
||||
} else if (keyEditType.value === 'delete') {
|
||||
keywordList.value = keywordList.value.filter(
|
||||
item => !multipleKeywordSelection.value.includes(item)
|
||||
)
|
||||
} else if (keyEditType.value === 'deleteall') {
|
||||
keywordList.value = []
|
||||
}
|
||||
|
||||
updateKeywordsToForm()
|
||||
keyVisible.value = false
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const FileRef = ref()
|
||||
const openForm = (type: string) => {
|
||||
FileRef.value.open(type)
|
||||
}
|
||||
|
||||
// 媒体文件
|
||||
const mediumList = ref([] as any)
|
||||
const multipleMediumSelection = ref([] as any)
|
||||
const handleMediumSelectionChange = (val: any) => {
|
||||
multipleMediumSelection.value = val
|
||||
}
|
||||
const fileList = []
|
||||
|
||||
|
||||
const multipleDocumentSelection = ref([] as any)
|
||||
const handleDocumentSelectionChange = (val: any) => {
|
||||
multipleDocumentSelection.value = val
|
||||
}
|
||||
//文件
|
||||
const handleUploadSuccess = ({ url, fileType }) => {
|
||||
const index = formData.value.fileUploads.findIndex(item => item.fileType === fileType)
|
||||
if (index !== -1) {
|
||||
formData.value.fileUploads[index].url = url
|
||||
}
|
||||
}
|
||||
|
||||
const downloadFile = async (url: string) => {
|
||||
if (!url) {
|
||||
ElMessage.warning('暂无可下载的文件地址')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
if (!response.ok) {
|
||||
throw new Error('下载失败')
|
||||
}
|
||||
|
||||
const blob = await response.blob()
|
||||
const blobUrl = window.URL.createObjectURL(blob)
|
||||
|
||||
// 提取文件名
|
||||
const filename = url.substring(url.lastIndexOf('/') + 1).split('?')[0]
|
||||
|
||||
// 创建 a 标签并下载
|
||||
const a = document.createElement('a')
|
||||
a.href = blobUrl
|
||||
a.download = filename
|
||||
a.style.display = 'none'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
a.remove()
|
||||
|
||||
window.URL.revokeObjectURL(blobUrl)
|
||||
} catch (err: any) {
|
||||
ElMessage.error(`下载失败:${err.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const deleteUrl = (index: number) => {
|
||||
formData.value.fileUploads[index].url = ''
|
||||
formData.value.fileUploads[index].fileName = ''
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** 打开弹窗 */
|
||||
const open = async (queryParams: any ,type: string, id?: number) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = t('action.' + type)
|
||||
formType.value = type
|
||||
rightActiveName.value = 'analysis'
|
||||
// 修改时,设置数据
|
||||
if (id) {
|
||||
kaodianData.value.quId=id;
|
||||
formLoading.value = true
|
||||
try {
|
||||
const res = await QuestionApi.getQuestionnotId(id);
|
||||
|
||||
// 默认两个类型
|
||||
const fileTypes = ['1', '2'];
|
||||
|
||||
// 后端返回的上传文件列表(可能为空)
|
||||
const documentList = res.fileUploads ?? [];
|
||||
|
||||
// 遍历两种类型,找到对应的上传文件,如果没有就用默认值
|
||||
const fileUploads = fileTypes.map(type => {
|
||||
const match = documentList.find(file => file.fileType === type);
|
||||
return {
|
||||
quId: match?.quId ?? res.quId ?? '',
|
||||
url: match?.url ?? '',
|
||||
fileType: type,
|
||||
fileName: match?.fileName ?? ''
|
||||
};
|
||||
});
|
||||
|
||||
formData.value = {
|
||||
...res,
|
||||
fileUploads,
|
||||
};
|
||||
|
||||
|
||||
keywordList.value = res.keywords
|
||||
? res.keywords.split(',').filter(item => item.trim() !== '').map(item => ({ keyword: item.trim() }))
|
||||
: []
|
||||
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
}
|
||||
} else {
|
||||
resetForm()
|
||||
formData.value.specialtyName = queryParams.specialtyName
|
||||
formData.value.courseName = queryParams.courseName
|
||||
formData.value.subjectName = queryParams.subjectName
|
||||
formData.value.pointNames=queryParams.pointNames
|
||||
formData.value.chapteridDictText=queryParams.chapteridDictText
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
// 转换函数:将大写字母 A-Z 映射为 0-25
|
||||
const mappedNumber = computed(() => {
|
||||
const char = radio.value.toUpperCase();
|
||||
const code = char.charCodeAt(0);
|
||||
if (code >= 65 && code <= 90) {
|
||||
return code - 65;
|
||||
} else {
|
||||
return '请输入 A-Z 的字母';
|
||||
}
|
||||
});
|
||||
// 选择答案数据
|
||||
const answerData = ref([]);
|
||||
/** 提交表单 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitForm = async () => {
|
||||
// 校验表单
|
||||
if (!formRef) return;
|
||||
const valid = await formRef.value.validate();
|
||||
if (!valid) return;
|
||||
|
||||
formLoading.value = true;
|
||||
try {
|
||||
// 深拷贝一份 formData
|
||||
const data = JSON.parse(JSON.stringify(formData.value));
|
||||
|
||||
// 过滤掉 url 为空的文件项
|
||||
data.fileUploads = data.fileUploads?.filter(file => file.url && file.url.trim() !== '');
|
||||
|
||||
|
||||
await QuestionApi.editQuestionNoAudit(data)
|
||||
message.success(t('common.updateSuccess'));
|
||||
|
||||
|
||||
dialogVisible.value = false;
|
||||
emit('success');
|
||||
} finally {
|
||||
formLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
content: `---------------------------------------------------------------------
|
||||
请在打开的窗口中,进行下列操作,完成所有操作后,请关闭窗口。
|
||||
---------------------------------------------------------------------
|
||||
`,
|
||||
specialtyName: '',
|
||||
courseName: '',
|
||||
quBankName: '',
|
||||
required: '',
|
||||
chapteridDictText: '',
|
||||
analysis: '',
|
||||
quLevel: '0',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: '0',
|
||||
keywords: '',
|
||||
resourceValue: '',
|
||||
fileUploads: [ {
|
||||
quId: '',
|
||||
url: '',
|
||||
fileType: '1',
|
||||
fileName: ''
|
||||
},
|
||||
{
|
||||
quId: '',
|
||||
url: '',
|
||||
fileType: '2',
|
||||
fileName: ''
|
||||
}]
|
||||
}
|
||||
keywordList.value=[],
|
||||
formRef.value?.resetFields()
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.edit-dialog {
|
||||
:deep(.el-dialog) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.el-dialog__header {
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
border-top: 1px solid #ededed;
|
||||
}
|
||||
.el-dialog__body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
.main {
|
||||
.el-form {
|
||||
padding: 10px;
|
||||
.el-row {
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.el-form-item {
|
||||
width: 100%;
|
||||
margin-bottom: 0;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.edit-bottom {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 10px;
|
||||
.bottom-common {
|
||||
width: calc(50% - 10px);
|
||||
.el-tabs {
|
||||
height: 100%;
|
||||
.el-tabs__content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
.block {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
overflow: auto;
|
||||
}
|
||||
.answer {
|
||||
.tip {
|
||||
color: #8a6d3b;
|
||||
background-color: #fcf8e3;
|
||||
border-color: #faebcc;
|
||||
padding: 10px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> p {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
.el-radio-group {
|
||||
width: 100%;
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
font-size: 0;
|
||||
flex-direction: column;
|
||||
.options {
|
||||
width: 100%;
|
||||
margin-top: 15px;
|
||||
.content {
|
||||
display: flex;
|
||||
.text {
|
||||
width: 100%;
|
||||
height: 70px;
|
||||
border: 1px solid #ededed;
|
||||
.el-textarea__inner {
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.more-btn {
|
||||
margin-top: 8px;
|
||||
background-color: #ffffff;
|
||||
border-color: #007bff;
|
||||
color: #007bff;
|
||||
width: auto;
|
||||
height: auto;
|
||||
padding: 10px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-tabs-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.setting_icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url('@/assets/icon/setting_blue.png') no-repeat center;
|
||||
background-size: 100%;
|
||||
margin-left: 3px;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.is-active {
|
||||
.custom-tabs-label {
|
||||
.setting_icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ele-pro-table) {
|
||||
flex: 1;
|
||||
margin-top: 10px;
|
||||
.el-table--fit {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.tox-tinymce) {
|
||||
.tox-statusbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
.el-table__header-wrapper {
|
||||
.el-table__header {
|
||||
thead {
|
||||
tr {
|
||||
th {
|
||||
background: #ebebeb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
1149
src/views/paper/audit/MysqlForm.vue
Normal file
1149
src/views/paper/audit/MysqlForm.vue
Normal file
File diff suppressed because it is too large
Load Diff
107
src/views/paper/audit/components/FileForm.vue
Normal file
107
src/views/paper/audit/components/FileForm.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<Dialog v-model="dialogVisible" title="上传文件">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
v-model:file-list="fileList"
|
||||
:action="uploadUrl"
|
||||
:auto-upload="false"
|
||||
:data="data"
|
||||
:disabled="formLoading"
|
||||
:limit="1"
|
||||
:on-change="handleFileChange"
|
||||
:on-error="submitFormError"
|
||||
:on-exceed="handleExceed"
|
||||
:on-success="submitFormSuccess"
|
||||
:http-request="httpRequest"
|
||||
drag
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text"> 将文件拖到此处,或 <em>点击上传</em></div>
|
||||
<template #tip>
|
||||
<!-- <div class="el-upload__tip" style="color: red">
|
||||
提示:仅允许导入 jpg、png、gif 格式文件!
|
||||
</div> -->
|
||||
</template>
|
||||
</el-upload>
|
||||
<template #footer>
|
||||
<el-button :disabled="formLoading" type="primary" @click="submitFileForm">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUpload } from '@/components/UploadFile/src/useUpload'
|
||||
|
||||
defineOptions({ name: 'InfraFileForm' })
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||
const formLoading = ref(false) // 表单的加载中
|
||||
const fileList = ref([]) // 文件列表
|
||||
const data = ref({ path: '' })
|
||||
const uploadRef = ref()
|
||||
|
||||
const { uploadUrl, httpRequest } = useUpload()
|
||||
const currentType = ref(null)
|
||||
/** 打开弹窗 */
|
||||
const open = async (type) => {
|
||||
currentType.value = type // 记录类型
|
||||
console.log(currentType.value+"123123")
|
||||
dialogVisible.value = true
|
||||
resetForm()
|
||||
}
|
||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||
|
||||
/** 处理上传的文件发生变化 */
|
||||
const handleFileChange = (file) => {
|
||||
data.value.path = file.name
|
||||
}
|
||||
|
||||
/** 提交表单 */
|
||||
const submitFileForm = () => {
|
||||
if (fileList.value.length == 0) {
|
||||
message.error('请上传文件')
|
||||
return
|
||||
}
|
||||
unref(uploadRef)?.submit()
|
||||
}
|
||||
|
||||
/** 文件上传成功处理 */
|
||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||
const submitFormSuccess = (response: any, file: any) => {
|
||||
// response 是后端接口返回的数据
|
||||
// file 是当前上传的文件对象
|
||||
|
||||
// 假设后端返回格式:{ url: '文件地址', fileName: '文件名' }
|
||||
const url = response.data
|
||||
console.log(currentType.value+"v")
|
||||
dialogVisible.value = false
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
message.success(t('common.createSuccess'))
|
||||
|
||||
// 触发成功事件,携带文件信息传递给父组件
|
||||
emit('success', { url, fileType: currentType.value })
|
||||
}
|
||||
|
||||
|
||||
/** 上传错误提示 */
|
||||
const submitFormError = (): void => {
|
||||
message.error('上传失败,请您重新上传!')
|
||||
formLoading.value = false
|
||||
}
|
||||
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
// 重置上传状态和文件
|
||||
formLoading.value = false
|
||||
uploadRef.value?.clearFiles()
|
||||
}
|
||||
|
||||
/** 文件数超出提示 */
|
||||
const handleExceed = (): void => {
|
||||
message.error('最多只能上传一个文件!')
|
||||
}
|
||||
</script>
|
||||
@@ -295,6 +295,12 @@
|
||||
<UserImportForm ref="importFormRef" @success="getList" />
|
||||
<!-- 分配角色 -->
|
||||
<UserAssignRoleForm ref="assignRoleFormRef" @success="getList" />
|
||||
|
||||
<MdesignForm ref="mformRef" @success="getList" />
|
||||
|
||||
<BdesignForm ref="bformRef" @success="getList" />
|
||||
|
||||
<FdesignForm ref="fformRef" @success="getList" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { dateFormatter } from '@/utils/formatTime'
|
||||
@@ -305,7 +311,9 @@ import * as QuestionApi from '@/api/paper/question'
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
import ChoiceForm from './ChoiceForm.vue'
|
||||
import CdesignForm from './CdesignForm.vue'
|
||||
|
||||
import MdesignForm from './MysqlForm.vue'
|
||||
import BdesignForm from './BrowerForm.vue'
|
||||
import FdesignForm from './FileForm.vue'
|
||||
import UserImportForm from './UserImportForm.vue'
|
||||
import UserAssignRoleForm from './UserAssignRoleForm.vue'
|
||||
import {handleTree} from "@/utils/tree";
|
||||
@@ -624,17 +632,30 @@ const findNamePathFromTreeList = (treeList: Tree[], targetId: number): string[]
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref()
|
||||
const cformRef = ref()
|
||||
const mformRef = ref()
|
||||
const bformRef = ref()
|
||||
const fformRef = ref()
|
||||
const wformRef = ref()
|
||||
const openForm = (type: string, row: any) => {
|
||||
console.log(queryParams)
|
||||
// if (queryParams.subjectName == "") {
|
||||
// return message.confirm('请选择题型!');
|
||||
// }
|
||||
// if (chooseQuestionType.value.includes("选择题")){
|
||||
// cformRef.value.open(type, id)
|
||||
// } else {
|
||||
// formRef.value.open(queryParams, type, id)
|
||||
// }
|
||||
formRef.value.open(queryParams, type, row.quId)
|
||||
console.log(row.subjectName+"subjectName");
|
||||
if (row.subjectName == "") {
|
||||
return message.confirm('请选择题型!');
|
||||
}
|
||||
if (row.subjectName.includes("选择题")){
|
||||
|
||||
console.log(row.quId+"row.quId")
|
||||
formRef.value.open(queryParams,type, row.quId)
|
||||
} else if(row.subjectName.includes("编程题")) {
|
||||
cformRef.value.open(queryParams,type, row.quId)
|
||||
}else if(row.subjectName.includes("程序设计")) {
|
||||
mformRef.value.open(queryParams,type, row.quId)
|
||||
}else if(row.subjectName.includes("网络题")) {
|
||||
bformRef.value.open(queryParams,type, row.quId)
|
||||
}else if(row.subjectName.includes("文件处理")) {
|
||||
fformRef.value.open(queryParams,type, row.quId)
|
||||
} else if (row.subjectName.includes("文字")){
|
||||
wformRef.value.open(queryParams,type, row.quId)
|
||||
}
|
||||
}
|
||||
|
||||
/** 用户导入 */
|
||||
|
||||
@@ -447,7 +447,6 @@ const formData = ref({
|
||||
analysis: '',
|
||||
quLevel: '',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: ' ',
|
||||
keywords: '',
|
||||
@@ -725,7 +724,9 @@ formData.value = {
|
||||
...res,
|
||||
fileUploads,
|
||||
};
|
||||
|
||||
keywordList.value = res.keywords
|
||||
? res.keywords.split(',').filter(item => item.trim() !== '').map(item => ({ keyword: item.trim() }))
|
||||
: []
|
||||
} catch (error) {
|
||||
console.error("获取问题失败:", error);
|
||||
} finally {
|
||||
@@ -793,10 +794,7 @@ const submitForm = async () => {
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
content: `---------------------------------------------------------------------
|
||||
请在打开的窗口中,进行下列操作,完成所有操作后,请关闭窗口。
|
||||
---------------------------------------------------------------------
|
||||
`,
|
||||
content: `<p>---------------------------------------------------------------------</p><p> 请在打开的窗口中,进行下列操作,完成所有操作后,请关闭窗口。</p><p>---------------------------------------------------------------------</p><p>`,
|
||||
specialtyName: '',
|
||||
courseName: '',
|
||||
quBankName: '',
|
||||
@@ -805,7 +803,6 @@ const resetForm = () => {
|
||||
analysis: '',
|
||||
quLevel: '0',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: '0',
|
||||
keywords: '',
|
||||
|
||||
@@ -65,18 +65,13 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="启用状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-form-item label="启用状态" prop="status">
|
||||
<el-radio-group v-model="formData.status">
|
||||
<el-radio :label="'0'">启用</el-radio>
|
||||
<el-radio :label="'1'">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
|
||||
@@ -436,7 +436,7 @@ const dialogTitle = ref('') // 弹窗的标题
|
||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref({
|
||||
content: '',
|
||||
content: '<p>---------------------------------------------------------------------</p><p> 请在打开的窗口中,进行下列操作,完成所有操作后,请关闭窗口。</p><p>---------------------------------------------------------------------</p><p>',
|
||||
specialtyName: '',
|
||||
courseName: '',
|
||||
quBankName: '',
|
||||
@@ -445,7 +445,6 @@ const formData = ref({
|
||||
analysis: '',
|
||||
quLevel: '',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: ' ',
|
||||
keywords: '',
|
||||
@@ -749,7 +748,9 @@ formData.value = {
|
||||
};
|
||||
|
||||
|
||||
keywordList.value = res.keywords.split(',').map(item => ({ keyword: item.trim() }));
|
||||
keywordList.value = res.keywords
|
||||
? res.keywords.split(',').filter(item => item.trim() !== '').map(item => ({ keyword: item.trim() }))
|
||||
: []
|
||||
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
@@ -816,9 +817,7 @@ const submitForm = async () => {
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
content: `---------------------------------------------------------------------
|
||||
请在打开的窗口中,进行下列操作,完成所有操作后,请关闭窗口。
|
||||
---------------------------------------------------------------------
|
||||
content: `<p>---------------------------------------------------------------------</p><p> 请在打开的窗口中,进行下列操作,完成所有操作后,请关闭窗口。</p><p>---------------------------------------------------------------------</p><p>
|
||||
`,
|
||||
specialtyName: '',
|
||||
courseName: '',
|
||||
@@ -828,7 +827,6 @@ const resetForm = () => {
|
||||
analysis: '',
|
||||
quLevel: '0',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: '0',
|
||||
keywords: '',
|
||||
|
||||
@@ -443,7 +443,7 @@ const formData = ref({
|
||||
analysis: '',
|
||||
quLevel: '',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
|
||||
subjectName: '',
|
||||
status: ' ',
|
||||
keywords: '',
|
||||
@@ -868,7 +868,9 @@ formData.value = {
|
||||
...res,
|
||||
fileUploads,
|
||||
};
|
||||
|
||||
keywordList.value = res.keywords
|
||||
? res.keywords.split(',').filter(item => item.trim() !== '').map(item => ({ keyword: item.trim() }))
|
||||
: []
|
||||
|
||||
} finally {
|
||||
formLoading.value = false
|
||||
@@ -936,14 +938,7 @@ const submitForm = async () => {
|
||||
/** 重置表单 */
|
||||
const resetForm = () => {
|
||||
formData.value = {
|
||||
content: `---------------------------------------------------------------------------
|
||||
注意事项:
|
||||
请利用图形化管理界面或者MySQL命令行工具,在指定的试题数据库中进行答题。
|
||||
MySQL连接地址为localhost
|
||||
MySQL登录用户名为root
|
||||
MySQL端口号为6033或3306
|
||||
MySQL密码为空
|
||||
---------------------------------------------------------------------------`,
|
||||
content: `<p style="text-align: left;"><span style="font-family: 宋体;">--------------------------------------------------------------------------- </span></p><p style="text-align: left;"><span style="font-family: 宋体;">注意事项:</span></p><p style="text-align: left;"><span style="font-family: 宋体;">请利用图形化管理界面或者MySQL命令行工具,在指定的试题数据库中进行答题。 </span></p><p style="text-align: left;"><span style="font-family: 宋体;">MySQL连接地址为localhost</span></p><p style="text-align: left;"><span style="font-family: 宋体;">MySQL登录用户名为root</span></p><p style="text-align: left;"><span style="font-family: 宋体;">MySQL端口号为3306或者6033</span></p><p style="text-align: left;"><span style="font-family: 宋体;">MySQL密码为空</span></p><p style="text-align: left;"><span style="font-family: 宋体;">---------------------------------------------------------------------------</span>`,
|
||||
specialtyName: '',
|
||||
courseName: '',
|
||||
quBankName: '',
|
||||
@@ -952,7 +947,6 @@ MySQL密码为空
|
||||
analysis: '',
|
||||
quLevel: '0',
|
||||
pointNames: '',
|
||||
audit: '',
|
||||
subjectName: '',
|
||||
status: '0',
|
||||
keywords: '',
|
||||
|
||||
@@ -1,181 +1,634 @@
|
||||
<template>
|
||||
<Dialog v-model="visible" :title="'编辑试卷'" width="80%" height="10%" @open="handleOpen" center >
|
||||
<ContentWrap>
|
||||
<el-button @click="handleSelect"> <Icon icon="ep:refresh" class="mr-5px" />刷新</el-button>
|
||||
<el-button @click="handleChange"><Icon icon="ep:sort" class="mr-5px" /> 换题</el-button>
|
||||
|
||||
<div style="display: flex; gap: 20px; height: 500px; width: 100%;">
|
||||
<!-- 左侧:题目表格 -->
|
||||
<div style="flex: 3; overflow: auto;">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="questionList"
|
||||
@row-click="handleQuestionRowClick"
|
||||
highlight-current-row
|
||||
ref="questionTableRef"
|
||||
>
|
||||
<el-table-column label="序号" prop="index" align="center" width="60" />
|
||||
<el-table-column label="编号" prop="questionIndex" align="center" width="80" />
|
||||
<el-table-column label="题型" prop="subjectName" align="center" />
|
||||
<el-table-column label="难度" prop="quLevel" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.EXAM_QUE_DIFF" :value="scope.row.quLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="知识点" prop="pointNames" align="left" />
|
||||
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 右侧:题目详情 -->
|
||||
<div style=" flex: 4; border: 1px solid #ebeef5; padding: 16px; border-radius: 4px; overflow: auto; height: 100%;">
|
||||
<template v-if="selectedQuestion">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【题目描述】</div>
|
||||
<div v-html="selectedQuestion.content " style="margin-bottom: 16px;"></div>
|
||||
|
||||
<template v-if="isChoiceQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【选项及答案】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div v-for="(answer, index) in selectedQuestion.answerList" :key="answer.answerId" style="margin-bottom: 4px;">
|
||||
{{ optionLabel(index) }}. {{ answer.content }}
|
||||
<span v-if="answer.isRight === '0'" style="color: green; font-weight: bold">(正确)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
<Dialog v-model="visible" :title="'修改试卷'" width="460" @open="handleOpen" center>
|
||||
|
||||
|
||||
<!-- <div style="margin-bottom: 16px">
|
||||
<strong>试卷ID:</strong>{{ paperId }}
|
||||
</div> -->
|
||||
|
||||
<!-- 滚动区域 -->
|
||||
<div style="max-height: 400px; overflow-y: auto;">
|
||||
<!-- 分组展示题目 -->
|
||||
<div
|
||||
v-for="(items, subject) in sortedGroupedQuestions"
|
||||
:key="subject"
|
||||
>
|
||||
<h4 class="mb-2 text-base text-gray-700 font-medium">
|
||||
{{ subject }}
|
||||
<span v-if="schemeMap[subject]" class="text-sm text-gray-500 font-normal">
|
||||
(每小题 {{ parseFloat(schemeMap[subject].quScores).toFixed(1) }} 分,共 {{ parseFloat(schemeMap[subject].subtotalScore).toFixed(1) }} 分)
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
|
||||
|
||||
<el-card
|
||||
v-for="(item, index) in items"
|
||||
:key="item.quId"
|
||||
class="mb-3"
|
||||
shadow="never"
|
||||
>
|
||||
<div class="text-gray-500 mb-2">题目 {{ index + 1 }}</div>
|
||||
<div v-html="item.content" class="mb-2"></div>
|
||||
|
||||
<!-- 🎯 判断是否为编程题 -->
|
||||
<div v-if="item.subjectName === '编程题'">
|
||||
<!-- 编程题解析 -->
|
||||
<div class="mt-2">
|
||||
<div class="font-bold text-blue-600">解析</div>
|
||||
<pre
|
||||
style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;"
|
||||
>{{ item.analysis }}</pre>
|
||||
<!-- 程序设计题 -->
|
||||
<template v-else-if="isProgrammingQuestion(selectedQuestion.subjectName)">
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div style="margin-bottom: 8px; white-space: pre-wrap;">
|
||||
<strong>【考点语句】:</strong><br />
|
||||
{{ answer.content }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(answer, aIndex) in item.answerList"
|
||||
:key="answer.answerId"
|
||||
class="mb-1"
|
||||
>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}%
|
||||
|
||||
<div v-if="answer.examMysqlKeywordList && answer.examMysqlKeywordList.length">
|
||||
<div style="margin-top: 10px;"><strong>【关键词列表】:</strong></div>
|
||||
<ul style="padding-left: 20px;">
|
||||
<li
|
||||
v-for="keyword in answer.examMysqlKeywordList"
|
||||
:key="keyword.keywordId"
|
||||
style="line-height: 1.6;"
|
||||
>
|
||||
- 关键词:{{ keyword.keyword }},权值:{{ keyword.scoreRate }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 📝 普通题目的选项显示 -->
|
||||
<div v-else-if="item.answerList && item.answerList.length">
|
||||
<div
|
||||
v-for="(answer, aIndex) in item.answerList.slice().sort((a, b) => a.sort - b.sort)"
|
||||
:key="answer.answerId"
|
||||
:style="{
|
||||
color: answer.isRight === '0' ? 'green' : 'inherit',
|
||||
fontWeight: answer.isRight === '0' ? 'bold' : 'normal'
|
||||
}"
|
||||
class="mb-1"
|
||||
>
|
||||
{{ String.fromCharCode(65 + aIndex) }}. {{ answer.content }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无程序设计答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 编程题 -->
|
||||
<template v-else-if="isCodingTestQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【答案】</div>
|
||||
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;">
|
||||
{{ selectedQuestion.analysis }}
|
||||
</pre>
|
||||
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分测试用例】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【输入】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【输出】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无测试用例信息</div>
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分关键字】</div>
|
||||
<div v-if="selectedQuestion.questionKeywords && selectedQuestion.questionKeywords.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.questionKeywords"
|
||||
:key="answer.keywordId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【关键字】:</strong>{{ answer.keyword }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无关键字信息</div>
|
||||
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分标准】</div>
|
||||
<div v-if="selectedQuestion.questionScores">
|
||||
<div
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【检查程序编译】:</strong>{{ selectedQuestion.questionScores.isPass === '0' ? '是' : '否' }} , <strong>【检查程序编译百分比】:</strong>{{ selectedQuestion.questionScores.isPassScore }}%</div>
|
||||
<div><strong>【检查程序结果】:</strong>{{ selectedQuestion.questionScores.isResult === '0' ? '是' : '否' }} ,<strong>【检查程序结果百分比】:</strong>{{ selectedQuestion.questionScores.isResultScore }}%</div>
|
||||
<div><strong>【检查关键字】:</strong>{{ selectedQuestion.questionScores.isKeyword === '0' ? '是' : '否' }} , <strong>【检查关键字百分比】:</strong>{{ selectedQuestion.questionScores.isKeywordScore }}%</div>
|
||||
<div><strong>【使用测试用例】:</strong>{{ selectedQuestion.questionScores.isCompile === '0' ? '是' : '否' }} , <strong>【使用测试用例百分比】:</strong>{{ selectedQuestion.questionScores.isCompileScore }}%</div>
|
||||
|
||||
<div><strong>【关键字得分临界值】:</strong>{{ selectedQuestion.questionScores.keywordCutoff }}%</div>
|
||||
<div><strong>【测试用例得分临界值(小于等于测试用例个数)】:</strong>{{ selectedQuestion.questionScores.compileCutoff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无输入输出信息</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<template v-else>
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div v-for="answer in selectedQuestion.answerList" :key="answer.answerId" style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace">
|
||||
<div><strong>【考察点】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【考察类型】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无考点信息</div>
|
||||
</template>
|
||||
|
||||
|
||||
</template>
|
||||
</Dialog>
|
||||
<template v-else>
|
||||
<div style="color: #999;">请点击左侧试题查看详细内容</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model="visibleChange" :title="'换题列表'" width="80%" center @close="handleCancelQue" >
|
||||
<ContentWrap>
|
||||
<div style="display: flex; gap: 20px;height: 500px; width: 100%;">
|
||||
|
||||
<!-- 左侧题目表格 -->
|
||||
<div style="flex: 3;">
|
||||
|
||||
<!-- 搜索条件区域 -->
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
||||
<el-input
|
||||
v-model="queryParams.pointNames"
|
||||
placeholder="试题知识点"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
/>
|
||||
<el-select
|
||||
v-model="queryParams.quLevel"
|
||||
placeholder="难度"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
>
|
||||
<el-option label="简单" value="0" />
|
||||
<el-option label="一般" value="1" />
|
||||
<el-option label="困难" value="2" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="queryParams.quId"
|
||||
placeholder="题号"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
/>
|
||||
<el-button type="primary" @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button>
|
||||
<el-button @click="resetSearch"> <Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
|
||||
<el-button @click="resetRandom" > 随机换题</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
ref="changeTableRef"
|
||||
highlight-current-row
|
||||
@row-click="handleRowClick"
|
||||
>
|
||||
|
||||
<el-table-column label="试题编号" align="center" key="id" prop="quId" :show-overflow-tooltip="true"/>
|
||||
<el-table-column label="专业" align="center" prop="specialtyName" width="120" />
|
||||
<el-table-column label="课程" align="center" prop="courseName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="章节名称" align="center" prop="chapteridDictText" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="难度" align="center" prop="quLevel" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.EXAM_QUE_DIFF" :value="scope.row.quLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="知识点" align="center" prop="pointNames" :show-overflow-tooltip="true" />
|
||||
<!-- <el-table-column label="审核状态" align="center" prop="audit" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.QUESTION_AUDIT" :value="scope.row.audit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" key="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYS_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
</el-table>
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 右侧题目详情 -->
|
||||
<div style="flex: 4; border: 1px solid #ebeef5; padding: 16px; border-radius: 4px; overflow-y: auto;">
|
||||
<template v-if="selectedSwapQuestion">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【题目描述】</div>
|
||||
<div v-html="selectedSwapQuestion.content" style="margin-bottom: 16px;"></div>
|
||||
|
||||
<template v-if="isChoiceQuestion(selectedSwapQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【选项及答案】</div>
|
||||
<div v-if="selectedSwapQuestion.answerList && selectedSwapQuestion.answerList.length">
|
||||
<div
|
||||
v-for="(answer, index) in selectedSwapQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 4px;"
|
||||
>
|
||||
{{ optionLabel(index) }}. {{ answer.content }}
|
||||
<span v-if="answer.isRight === '0'" style="color: green; font-weight: bold">(正确)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<!-- 编程题 -->
|
||||
<template v-else-if="isCodingTestQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【答案】</div>
|
||||
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;">
|
||||
{{ selectedQuestion.analysis }}
|
||||
</pre>
|
||||
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分测试用例】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【输入】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【输出】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无测试用例信息</div>
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分关键字】</div>
|
||||
<div v-if="selectedQuestion.questionKeywords && selectedQuestion.questionKeywords.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.questionKeywords"
|
||||
:key="answer.keywordId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【关键字】:</strong>{{ answer.keyword }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无关键字信息</div>
|
||||
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分标准】</div>
|
||||
<div v-if="selectedQuestion.questionScores">
|
||||
<div
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【检查程序编译】:</strong>{{ selectedQuestion.questionScores.isPass === '0' ? '是' : '否' }} , <strong>【检查程序编译百分比】:</strong>{{ selectedQuestion.questionScores.isPassScore }}%</div>
|
||||
<div><strong>【检查程序结果】:</strong>{{ selectedQuestion.questionScores.isResult === '0' ? '是' : '否' }} ,<strong>【检查程序结果百分比】:</strong>{{ selectedQuestion.questionScores.isResultScore }}%</div>
|
||||
<div><strong>【检查关键字】:</strong>{{ selectedQuestion.questionScores.isKeyword === '0' ? '是' : '否' }} , <strong>【检查关键字百分比】:</strong>{{ selectedQuestion.questionScores.isKeywordScore }}%</div>
|
||||
<div><strong>【使用测试用例】:</strong>{{ selectedQuestion.questionScores.isCompile === '0' ? '是' : '否' }} , <strong>【使用测试用例百分比】:</strong>{{ selectedQuestion.questionScores.isCompileScore }}%</div>
|
||||
|
||||
<div><strong>【关键字得分临界值】:</strong>{{ selectedQuestion.questionScores.keywordCutoff }}%</div>
|
||||
<div><strong>【测试用例得分临界值(小于等于测试用例个数)】:</strong>{{ selectedQuestion.questionScores.compileCutoff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无输入输出信息</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-else-if="isProgrammingQuestion(selectedQuestion.subjectName)">
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div style="margin-bottom: 8px; white-space: pre-wrap;">
|
||||
<strong>【考点语句】:</strong><br />
|
||||
{{ answer.content }}
|
||||
</div>
|
||||
|
||||
<div v-if="answer.examMysqlKeywordList && answer.examMysqlKeywordList.length">
|
||||
<div style="margin-top: 10px;"><strong>【关键词列表】:</strong></div>
|
||||
<ul style="padding-left: 20px;">
|
||||
<li
|
||||
v-for="keyword in answer.examMysqlKeywordList"
|
||||
:key="keyword.keywordId"
|
||||
style="line-height: 1.6;"
|
||||
>
|
||||
- 关键词:{{ keyword.keyword }},权值:{{ keyword.scoreRate }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无程序设计答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-else>
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedSwapQuestion.answerList && selectedSwapQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedSwapQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace"
|
||||
>
|
||||
<div><strong>考察点:</strong>{{ answer.content }}</div>
|
||||
<div><strong>考察类型:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无考点信息</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
<div style="color: #999;">请点击左侧题目查看详情</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancelQue">取 消</el-button>
|
||||
<el-button type="primary" @click="handleConfirmChange">确 定</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick, computed } from 'vue';
|
||||
import { useFormData } from '@/utils/use-form-data';
|
||||
import { getPaperDetailByTaskId } from '@/api/system/paper';
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps({
|
||||
paperId: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(['done']);
|
||||
const visible = defineModel({ type: Boolean });
|
||||
const loading = ref(false);
|
||||
const formRef = ref(null);
|
||||
const questionList = ref([]); // 用于存放试题列表
|
||||
const schemeMap = ref({});
|
||||
|
||||
const [form, resetFields, assignFields] = useFormData({
|
||||
paperId: void 0,
|
||||
taskId: '',
|
||||
counts: '',
|
||||
rollUp: '',
|
||||
isAb: '',
|
||||
status: ''
|
||||
});
|
||||
|
||||
const rules = reactive({});
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
</Dialog>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, nextTick, computed } from 'vue';
|
||||
import { getPaperDetailCenterByTaskId } from '@/api/system/paper';
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
const message = useMessage(); // 消息弹窗
|
||||
import * as QuestionApi from '@/api/paper/question'
|
||||
const props = defineProps({
|
||||
paperId: String // 接收父组件传来的 paperId
|
||||
});
|
||||
const queryParams = reactive({
|
||||
quLevel: "",
|
||||
pointNames: "",
|
||||
subjectName: "",
|
||||
quId:"",
|
||||
chapteridDictText:"",
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
audit:"0",
|
||||
status:"0",
|
||||
specialtyName:"",
|
||||
courseName:"",
|
||||
})
|
||||
const emit = defineEmits(['done']);
|
||||
const visible = defineModel({ type: Boolean }); // 对应 v-model
|
||||
const visibleChange = defineModel('visibleChange', { type: Boolean }); // 对应 v-model:visibleChange
|
||||
|
||||
const selectedChange = ref<any[]>([]);
|
||||
const selectedSwapQuestion = ref<any>(null); // 换题弹窗中点击的题目
|
||||
const questionTableRef = ref();
|
||||
const selectedQuestion = ref<any>(null); // 第一个弹窗选中的试题
|
||||
|
||||
const handleQuestionRowClick = (row: any) => {
|
||||
selectedQuestion.value = row;
|
||||
};
|
||||
|
||||
const questionList = ref<any[]>([]);
|
||||
|
||||
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数
|
||||
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
const handleChange =async () => {
|
||||
|
||||
if (!selectedQuestion.value) {
|
||||
message.warning('请先选中一个原题');
|
||||
return;
|
||||
}
|
||||
visibleChange.value = true;
|
||||
await getList();
|
||||
};
|
||||
|
||||
const handleSelect =async () => {
|
||||
await handleOpen();
|
||||
};
|
||||
|
||||
|
||||
const handleCancelQue = () => {
|
||||
visibleChange.value = false;
|
||||
list.value=[];
|
||||
selectedSwapQuestion.value = null; // 🔁 清空右侧题目内容
|
||||
selectedChange.value = []; // (可选)清空选中项
|
||||
list.value = [];
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
queryParams.pointNames = '';
|
||||
queryParams.quLevel = '';
|
||||
queryParams.quId = '';
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const resetRandom = () => {
|
||||
|
||||
const originalId = selectedQuestion.value.quId || selectedQuestion.value.id;
|
||||
const sub=selectedQuestion.value.subjectName;
|
||||
console.log('原题 ID:', originalId);
|
||||
console.log('试卷ID:',props.paperId)
|
||||
console.log('原题题型:',sub)
|
||||
|
||||
// 构造参数
|
||||
const payload = {
|
||||
paperId: props.paperId,
|
||||
oldQuId: originalId,
|
||||
subjectName:sub,
|
||||
newQuId: null,
|
||||
};
|
||||
|
||||
|
||||
|
||||
const groupedQuestions = ref({});
|
||||
|
||||
const subjectPriority = {
|
||||
'选择题': 1,
|
||||
'多选题': 2,
|
||||
'判断题': 3,
|
||||
'编程题': 4,
|
||||
'其他': 5
|
||||
};
|
||||
|
||||
const groupBySubjectName = (list) => {
|
||||
const group = {};
|
||||
|
||||
list.forEach((item) => {
|
||||
const subject = item.subjectName || '其他'; // 默认为'其他'
|
||||
if (!group[subject]) {
|
||||
group[subject] = [];
|
||||
}
|
||||
group[subject].push(item);
|
||||
|
||||
try {
|
||||
QuestionApi.changePaperQuRandom( payload); // 根据实际路径修改
|
||||
message.success('换题成功');
|
||||
} catch (error) {
|
||||
console.error('换题请求出错:', error);
|
||||
message.error('请求失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
|
||||
|
||||
const changeTableRef = ref();
|
||||
|
||||
|
||||
|
||||
const handleRowClick = (row: any) => {
|
||||
selectedChange.value = [row]; // 只选中一个换题
|
||||
selectedSwapQuestion.value = row;
|
||||
};
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
queryParams.subjectName=selectedQuestion.value.subjectName
|
||||
try {
|
||||
const data = await QuestionApi.getQuestionlistAnswer(queryParams);
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 打开弹窗时加载题目数据
|
||||
const handleOpen = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getPaperDetailCenterByTaskId(props.paperId);
|
||||
const rawList = res.examQuestionList || [];
|
||||
const schemeList = res.educationPaperSchemeList || [];
|
||||
|
||||
// 构建题型 => sort 数字映射,例如 { '选择题': 1, '文件处理': 2, ... }
|
||||
const sortMap = {};
|
||||
schemeList.forEach((scheme) => {
|
||||
sortMap[scheme.spName] = Number(scheme.sort);
|
||||
});
|
||||
|
||||
groupedQuestions.value = group;
|
||||
|
||||
// 先排序:根据题型的 sort 排序
|
||||
const sortedRawList = [...rawList].sort((a, b) => {
|
||||
return (sortMap[a.subjectName] || 999) - (sortMap[b.subjectName] || 999);
|
||||
});
|
||||
|
||||
// 每种题型的编号计数器
|
||||
const typeCounter = {};
|
||||
let globalIndex = 1;
|
||||
|
||||
questionList.value = sortedRawList.map((q) => {
|
||||
const groupIndex = sortMap[q.subjectName] || 0;
|
||||
typeCounter[groupIndex] = (typeCounter[groupIndex] || 0) + 1;
|
||||
|
||||
const questionIndex = `${groupIndex}.${typeCounter[groupIndex]}`;
|
||||
|
||||
return {
|
||||
...q,
|
||||
index: globalIndex++, // 全局序号 1、2、3…
|
||||
questionIndex, // 按题型分组的编号 1.1、1.2、2.1…
|
||||
};
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleConfirmChange = () => {
|
||||
|
||||
if (selectedChange.value.length !== 1) {
|
||||
message.warning('请选择一个换题');
|
||||
return;
|
||||
}
|
||||
|
||||
const originalId = selectedQuestion.value.quId || selectedQuestion.value.id;
|
||||
const newId = selectedChange.value[0].quId || selectedChange.value[0].id;
|
||||
|
||||
console.log('原题 ID:', originalId);
|
||||
console.log('换题 ID:', newId);
|
||||
console.log('试卷ID:',props.paperId)
|
||||
|
||||
// 构造参数
|
||||
const payload = {
|
||||
paperId: props.paperId,
|
||||
oldQuId: originalId,
|
||||
newQuId: newId,
|
||||
};
|
||||
|
||||
const sortedGroupedQuestions = computed(() => {
|
||||
return Object.keys(groupedQuestions.value)
|
||||
.sort((a, b) => (subjectPriority[a] || subjectPriority['其他']) - (subjectPriority[b] || subjectPriority['其他']))
|
||||
.reduce((acc, subject) => {
|
||||
acc[subject] = groupedQuestions.value[subject];
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
|
||||
const handleOpen = () => {
|
||||
resetFields();
|
||||
questionList.value = [];
|
||||
|
||||
if (props.paperId) {
|
||||
loading.value = true;
|
||||
getPaperDetailByTaskId(props.paperId)
|
||||
.then((res) => {
|
||||
const { educationPaperSchemeList, examQuestionList } = res.data || {};
|
||||
if (Array.isArray(examQuestionList)) {
|
||||
questionList.value = examQuestionList;
|
||||
groupBySubjectName(examQuestionList); // 分类
|
||||
}
|
||||
|
||||
if (Array.isArray(educationPaperSchemeList)) {
|
||||
// 构建以题型名为 key 的映射表(spName 与 scheme 对应)
|
||||
schemeMap.value = educationPaperSchemeList.reduce((acc, item) => {
|
||||
acc[item.spName] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
assignFields(res);
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(`获取试卷详情失败: ${e.message}`);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate?.();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
try {
|
||||
QuestionApi.changePaperQu( payload); // 根据实际路径修改
|
||||
|
||||
message.success('换题成功');
|
||||
|
||||
} catch (error) {
|
||||
console.error('换题请求出错:', error);
|
||||
message.error('请求失败,请稍后重试');
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
visibleChange.value = false;
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
const choiceSubjects = ["选择题", "单选题", "多选题"];
|
||||
const programmingSubjects = ["程序设计"];
|
||||
|
||||
function isChoiceQuestion(subjectName) {
|
||||
return choiceSubjects.some(item => subjectName.includes(item));
|
||||
}
|
||||
|
||||
function isProgrammingQuestion(subjectName) {
|
||||
return programmingSubjects.some(item => subjectName.includes(item));
|
||||
}
|
||||
const isCodingTestQuestion = (subjectName: string): boolean => {
|
||||
return subjectName === '编程题';
|
||||
};
|
||||
|
||||
function optionLabel(index) {
|
||||
return String.fromCharCode(65 + index); // A, B, C, D ...
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -44,13 +44,13 @@
|
||||
style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;"
|
||||
>{{ item.analysis }}</pre>
|
||||
</div>
|
||||
<div
|
||||
<!-- <div
|
||||
v-for="(answer, aIndex) in item.answerList"
|
||||
:key="answer.answerId"
|
||||
class="mb-1"
|
||||
>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}%
|
||||
</div>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 📝 普通题目的选项显示 -->
|
||||
|
||||
@@ -162,6 +162,7 @@
|
||||
v-model="showEdit"
|
||||
:data="current"
|
||||
ref="taskEditRef"
|
||||
:paper-Id="currentPaperId"
|
||||
@done="reload"
|
||||
/>
|
||||
<paper-look
|
||||
@@ -319,11 +320,15 @@ const openDown = async () => {
|
||||
}
|
||||
};
|
||||
// 打开编辑弹窗
|
||||
const openEdit = (type: string, row?: object) => {
|
||||
const openEdit = (type: string, row?: any)=> {
|
||||
if (row?.counts > 0) {
|
||||
ElMessage.warning('当前试卷使用次数大于0,不允许更换试题');
|
||||
return;
|
||||
}
|
||||
showEdit.value = true;
|
||||
current.value = row;
|
||||
currentPaperId.value = row.paperId ?? null;
|
||||
nextTick(() => {
|
||||
taskEditRef.value?.open(type, row);
|
||||
taskEditRef.value?.open(type, row.paperId);
|
||||
});
|
||||
};
|
||||
const currentPaperId = ref<number | null>(null);
|
||||
|
||||
@@ -128,26 +128,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
<el-col :span="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="测评时长" >
|
||||
<el-time-picker
|
||||
v-model="form.examTime"
|
||||
@@ -158,11 +139,30 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="定时检查与学生端联通性,每">
|
||||
<!-- <el-form-item label="定时检查与学生端联通性,每">
|
||||
<el-input-number v-model="form.isConnect" label="分钟" @change="handleFormChange" />
|
||||
<span>分钟传一次,断联直接交卷</span>
|
||||
</el-form-item>
|
||||
</el-form-item> -->
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
@@ -147,12 +147,12 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="定时检查与学生端联通性,每">
|
||||
<el-input-number v-model="form.isConnect" label="分钟" @change="handleFormChange" />
|
||||
<span>分钟传一次,断联直接交卷</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -129,25 +129,7 @@
|
||||
</el-col>
|
||||
|
||||
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
<el-col :span="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="测评时长" >
|
||||
<el-time-picker
|
||||
v-model="form.examTime"
|
||||
@@ -158,12 +140,30 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="定时检查与学生端联通性,每">
|
||||
<el-input-number v-model="form.isConnect" label="分钟" @change="handleFormChange" />
|
||||
<span>分钟传一次,断联直接交卷</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
|
||||
@@ -130,24 +130,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
<el-col :span="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="测评时长" >
|
||||
<el-time-picker
|
||||
v-model="form.examTime"
|
||||
@@ -158,12 +141,29 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="定时检查与学生端联通性,每">
|
||||
<el-input-number v-model="form.isConnect" label="分钟" @change="handleFormChange" />
|
||||
<span>分钟传一次,断联直接交卷</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
|
||||
@@ -130,23 +130,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="测评时长" >
|
||||
<el-time-picker
|
||||
@@ -157,13 +140,30 @@
|
||||
@change="handleFormChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
</el-col>
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="定时检查与学生端联通性,每">
|
||||
<el-input-number v-model="form.isConnect" label="分钟" @change="handleFormChange" />
|
||||
<span>分钟传一次,断联直接交卷</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
|
||||
@@ -1,181 +1,634 @@
|
||||
<template>
|
||||
<Dialog v-model="visible" :title="'编辑试卷'" width="80%" height="10%" @open="handleOpen" center >
|
||||
<ContentWrap>
|
||||
<el-button @click="handleSelect"> <Icon icon="ep:refresh" class="mr-5px" />刷新</el-button>
|
||||
<el-button @click="handleChange"><Icon icon="ep:sort" class="mr-5px" /> 换题</el-button>
|
||||
|
||||
<div style="display: flex; gap: 20px; height: 500px; width: 100%;">
|
||||
<!-- 左侧:题目表格 -->
|
||||
<div style="flex: 3; overflow: auto;">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="questionList"
|
||||
@row-click="handleQuestionRowClick"
|
||||
highlight-current-row
|
||||
ref="questionTableRef"
|
||||
>
|
||||
<el-table-column label="序号" prop="index" align="center" width="60" />
|
||||
<el-table-column label="编号" prop="questionIndex" align="center" width="80" />
|
||||
<el-table-column label="题型" prop="subjectName" align="center" />
|
||||
<el-table-column label="难度" prop="quLevel" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.EXAM_QUE_DIFF" :value="scope.row.quLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="知识点" prop="pointNames" align="left" />
|
||||
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 右侧:题目详情 -->
|
||||
<div style=" flex: 4; border: 1px solid #ebeef5; padding: 16px; border-radius: 4px; overflow: auto; height: 100%;">
|
||||
<template v-if="selectedQuestion">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【题目描述】</div>
|
||||
<div v-html="selectedQuestion.content " style="margin-bottom: 16px;"></div>
|
||||
|
||||
<template v-if="isChoiceQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【选项及答案】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div v-for="(answer, index) in selectedQuestion.answerList" :key="answer.answerId" style="margin-bottom: 4px;">
|
||||
{{ optionLabel(index) }}. {{ answer.content }}
|
||||
<span v-if="answer.isRight === '0'" style="color: green; font-weight: bold">(正确)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
<Dialog v-model="visible" :title="'修改试卷'" width="460" @open="handleOpen" center>
|
||||
|
||||
|
||||
<!-- <div style="margin-bottom: 16px">
|
||||
<strong>试卷ID:</strong>{{ paperId }}
|
||||
</div> -->
|
||||
|
||||
<!-- 滚动区域 -->
|
||||
<div style="max-height: 400px; overflow-y: auto;">
|
||||
<!-- 分组展示题目 -->
|
||||
<div
|
||||
v-for="(items, subject) in sortedGroupedQuestions"
|
||||
:key="subject"
|
||||
>
|
||||
<h4 class="mb-2 text-base text-gray-700 font-medium">
|
||||
{{ subject }}
|
||||
<span v-if="schemeMap[subject]" class="text-sm text-gray-500 font-normal">
|
||||
(每小题 {{ parseFloat(schemeMap[subject].quScores).toFixed(1) }} 分,共 {{ parseFloat(schemeMap[subject].subtotalScore).toFixed(1) }} 分)
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
|
||||
|
||||
<el-card
|
||||
v-for="(item, index) in items"
|
||||
:key="item.quId"
|
||||
class="mb-3"
|
||||
shadow="never"
|
||||
>
|
||||
<div class="text-gray-500 mb-2">题目 {{ index + 1 }}</div>
|
||||
<div v-html="item.content" class="mb-2"></div>
|
||||
|
||||
<!-- 🎯 判断是否为编程题 -->
|
||||
<div v-if="item.subjectName === '编程题'">
|
||||
<!-- 编程题解析 -->
|
||||
<div class="mt-2">
|
||||
<div class="font-bold text-blue-600">解析</div>
|
||||
<pre
|
||||
style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;"
|
||||
>{{ item.analysis }}</pre>
|
||||
<!-- 程序设计题 -->
|
||||
<template v-else-if="isProgrammingQuestion(selectedQuestion.subjectName)">
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div style="margin-bottom: 8px; white-space: pre-wrap;">
|
||||
<strong>【考点语句】:</strong><br />
|
||||
{{ answer.content }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(answer, aIndex) in item.answerList"
|
||||
:key="answer.answerId"
|
||||
class="mb-1"
|
||||
>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}%
|
||||
|
||||
<div v-if="answer.examMysqlKeywordList && answer.examMysqlKeywordList.length">
|
||||
<div style="margin-top: 10px;"><strong>【关键词列表】:</strong></div>
|
||||
<ul style="padding-left: 20px;">
|
||||
<li
|
||||
v-for="keyword in answer.examMysqlKeywordList"
|
||||
:key="keyword.keywordId"
|
||||
style="line-height: 1.6;"
|
||||
>
|
||||
- 关键词:{{ keyword.keyword }},权值:{{ keyword.scoreRate }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 📝 普通题目的选项显示 -->
|
||||
<div v-else-if="item.answerList && item.answerList.length">
|
||||
<div
|
||||
v-for="(answer, aIndex) in item.answerList.slice().sort((a, b) => a.sort - b.sort)"
|
||||
:key="answer.answerId"
|
||||
:style="{
|
||||
color: answer.isRight === '0' ? 'green' : 'inherit',
|
||||
fontWeight: answer.isRight === '0' ? 'bold' : 'normal'
|
||||
}"
|
||||
class="mb-1"
|
||||
>
|
||||
{{ String.fromCharCode(65 + aIndex) }}. {{ answer.content }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无程序设计答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 编程题 -->
|
||||
<template v-else-if="isCodingTestQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【答案】</div>
|
||||
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;">
|
||||
{{ selectedQuestion.analysis }}
|
||||
</pre>
|
||||
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分测试用例】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【输入】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【输出】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无测试用例信息</div>
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分关键字】</div>
|
||||
<div v-if="selectedQuestion.questionKeywords && selectedQuestion.questionKeywords.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.questionKeywords"
|
||||
:key="answer.keywordId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【关键字】:</strong>{{ answer.keyword }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无关键字信息</div>
|
||||
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分标准】</div>
|
||||
<div v-if="selectedQuestion.questionScores">
|
||||
<div
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【检查程序编译】:</strong>{{ selectedQuestion.questionScores.isPass === '0' ? '是' : '否' }} , <strong>【检查程序编译百分比】:</strong>{{ selectedQuestion.questionScores.isPassScore }}%</div>
|
||||
<div><strong>【检查程序结果】:</strong>{{ selectedQuestion.questionScores.isResult === '0' ? '是' : '否' }} ,<strong>【检查程序结果百分比】:</strong>{{ selectedQuestion.questionScores.isResultScore }}%</div>
|
||||
<div><strong>【检查关键字】:</strong>{{ selectedQuestion.questionScores.isKeyword === '0' ? '是' : '否' }} , <strong>【检查关键字百分比】:</strong>{{ selectedQuestion.questionScores.isKeywordScore }}%</div>
|
||||
<div><strong>【使用测试用例】:</strong>{{ selectedQuestion.questionScores.isCompile === '0' ? '是' : '否' }} , <strong>【使用测试用例百分比】:</strong>{{ selectedQuestion.questionScores.isCompileScore }}%</div>
|
||||
|
||||
<div><strong>【关键字得分临界值】:</strong>{{ selectedQuestion.questionScores.keywordCutoff }}%</div>
|
||||
<div><strong>【测试用例得分临界值(小于等于测试用例个数)】:</strong>{{ selectedQuestion.questionScores.compileCutoff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无输入输出信息</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<template v-else>
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div v-for="answer in selectedQuestion.answerList" :key="answer.answerId" style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace">
|
||||
<div><strong>【考察点】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【考察类型】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无考点信息</div>
|
||||
</template>
|
||||
|
||||
|
||||
</template>
|
||||
</Dialog>
|
||||
<template v-else>
|
||||
<div style="color: #999;">请点击左侧试题查看详细内容</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model="visibleChange" :title="'换题列表'" width="80%" center @close="handleCancelQue" >
|
||||
<ContentWrap>
|
||||
<div style="display: flex; gap: 20px;height: 500px; width: 100%;">
|
||||
|
||||
<!-- 左侧题目表格 -->
|
||||
<div style="flex: 3;">
|
||||
|
||||
<!-- 搜索条件区域 -->
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
||||
<el-input
|
||||
v-model="queryParams.pointNames"
|
||||
placeholder="试题知识点"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
/>
|
||||
<el-select
|
||||
v-model="queryParams.quLevel"
|
||||
placeholder="难度"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
>
|
||||
<el-option label="简单" value="0" />
|
||||
<el-option label="一般" value="1" />
|
||||
<el-option label="困难" value="2" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="queryParams.quId"
|
||||
placeholder="题号"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
/>
|
||||
<el-button type="primary" @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button>
|
||||
<el-button @click="resetSearch"> <Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
|
||||
<el-button @click="resetRandom" > 随机换题</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
ref="changeTableRef"
|
||||
highlight-current-row
|
||||
@row-click="handleRowClick"
|
||||
>
|
||||
|
||||
<el-table-column label="试题编号" align="center" key="id" prop="quId" :show-overflow-tooltip="true"/>
|
||||
<el-table-column label="专业" align="center" prop="specialtyName" width="120" />
|
||||
<el-table-column label="课程" align="center" prop="courseName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="章节名称" align="center" prop="chapteridDictText" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="难度" align="center" prop="quLevel" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.EXAM_QUE_DIFF" :value="scope.row.quLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="知识点" align="center" prop="pointNames" :show-overflow-tooltip="true" />
|
||||
<!-- <el-table-column label="审核状态" align="center" prop="audit" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.QUESTION_AUDIT" :value="scope.row.audit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" key="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYS_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
</el-table>
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 右侧题目详情 -->
|
||||
<div style="flex: 4; border: 1px solid #ebeef5; padding: 16px; border-radius: 4px; overflow-y: auto;">
|
||||
<template v-if="selectedSwapQuestion">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【题目描述】</div>
|
||||
<div v-html="selectedSwapQuestion.content" style="margin-bottom: 16px;"></div>
|
||||
|
||||
<template v-if="isChoiceQuestion(selectedSwapQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【选项及答案】</div>
|
||||
<div v-if="selectedSwapQuestion.answerList && selectedSwapQuestion.answerList.length">
|
||||
<div
|
||||
v-for="(answer, index) in selectedSwapQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 4px;"
|
||||
>
|
||||
{{ optionLabel(index) }}. {{ answer.content }}
|
||||
<span v-if="answer.isRight === '0'" style="color: green; font-weight: bold">(正确)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<!-- 编程题 -->
|
||||
<template v-else-if="isCodingTestQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【答案】</div>
|
||||
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;">
|
||||
{{ selectedQuestion.analysis }}
|
||||
</pre>
|
||||
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分测试用例】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【输入】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【输出】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无测试用例信息</div>
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分关键字】</div>
|
||||
<div v-if="selectedQuestion.questionKeywords && selectedQuestion.questionKeywords.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.questionKeywords"
|
||||
:key="answer.keywordId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【关键字】:</strong>{{ answer.keyword }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无关键字信息</div>
|
||||
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分标准】</div>
|
||||
<div v-if="selectedQuestion.questionScores">
|
||||
<div
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【检查程序编译】:</strong>{{ selectedQuestion.questionScores.isPass === '0' ? '是' : '否' }} , <strong>【检查程序编译百分比】:</strong>{{ selectedQuestion.questionScores.isPassScore }}%</div>
|
||||
<div><strong>【检查程序结果】:</strong>{{ selectedQuestion.questionScores.isResult === '0' ? '是' : '否' }} ,<strong>【检查程序结果百分比】:</strong>{{ selectedQuestion.questionScores.isResultScore }}%</div>
|
||||
<div><strong>【检查关键字】:</strong>{{ selectedQuestion.questionScores.isKeyword === '0' ? '是' : '否' }} , <strong>【检查关键字百分比】:</strong>{{ selectedQuestion.questionScores.isKeywordScore }}%</div>
|
||||
<div><strong>【使用测试用例】:</strong>{{ selectedQuestion.questionScores.isCompile === '0' ? '是' : '否' }} , <strong>【使用测试用例百分比】:</strong>{{ selectedQuestion.questionScores.isCompileScore }}%</div>
|
||||
|
||||
<div><strong>【关键字得分临界值】:</strong>{{ selectedQuestion.questionScores.keywordCutoff }}%</div>
|
||||
<div><strong>【测试用例得分临界值(小于等于测试用例个数)】:</strong>{{ selectedQuestion.questionScores.compileCutoff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无输入输出信息</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-else-if="isProgrammingQuestion(selectedQuestion.subjectName)">
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div style="margin-bottom: 8px; white-space: pre-wrap;">
|
||||
<strong>【考点语句】:</strong><br />
|
||||
{{ answer.content }}
|
||||
</div>
|
||||
|
||||
<div v-if="answer.examMysqlKeywordList && answer.examMysqlKeywordList.length">
|
||||
<div style="margin-top: 10px;"><strong>【关键词列表】:</strong></div>
|
||||
<ul style="padding-left: 20px;">
|
||||
<li
|
||||
v-for="keyword in answer.examMysqlKeywordList"
|
||||
:key="keyword.keywordId"
|
||||
style="line-height: 1.6;"
|
||||
>
|
||||
- 关键词:{{ keyword.keyword }},权值:{{ keyword.scoreRate }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无程序设计答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-else>
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedSwapQuestion.answerList && selectedSwapQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedSwapQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace"
|
||||
>
|
||||
<div><strong>考察点:</strong>{{ answer.content }}</div>
|
||||
<div><strong>考察类型:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无考点信息</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
<div style="color: #999;">请点击左侧题目查看详情</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancelQue">取 消</el-button>
|
||||
<el-button type="primary" @click="handleConfirmChange">确 定</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick, computed } from 'vue';
|
||||
import { useFormData } from '@/utils/use-form-data';
|
||||
import { getPaperDetailByTaskId } from '@/api/system/paper';
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps({
|
||||
paperId: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(['done']);
|
||||
const visible = defineModel({ type: Boolean });
|
||||
const loading = ref(false);
|
||||
const formRef = ref(null);
|
||||
const questionList = ref([]); // 用于存放试题列表
|
||||
const schemeMap = ref({});
|
||||
|
||||
const [form, resetFields, assignFields] = useFormData({
|
||||
paperId: void 0,
|
||||
taskId: '',
|
||||
counts: '',
|
||||
rollUp: '',
|
||||
isAb: '',
|
||||
status: ''
|
||||
});
|
||||
|
||||
const rules = reactive({});
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
</Dialog>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, nextTick, computed } from 'vue';
|
||||
import { getPaperDetailCenterByTaskId } from '@/api/system/paper';
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
const message = useMessage(); // 消息弹窗
|
||||
import * as QuestionApi from '@/api/paper/question'
|
||||
const props = defineProps({
|
||||
paperId: String // 接收父组件传来的 paperId
|
||||
});
|
||||
const queryParams = reactive({
|
||||
quLevel: "",
|
||||
pointNames: "",
|
||||
subjectName: "",
|
||||
quId:"",
|
||||
chapteridDictText:"",
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
audit:"0",
|
||||
status:"0",
|
||||
specialtyName:"",
|
||||
courseName:"",
|
||||
})
|
||||
const emit = defineEmits(['done']);
|
||||
const visible = defineModel({ type: Boolean }); // 对应 v-model
|
||||
const visibleChange = defineModel('visibleChange', { type: Boolean }); // 对应 v-model:visibleChange
|
||||
|
||||
const selectedChange = ref<any[]>([]);
|
||||
const selectedSwapQuestion = ref<any>(null); // 换题弹窗中点击的题目
|
||||
const questionTableRef = ref();
|
||||
const selectedQuestion = ref<any>(null); // 第一个弹窗选中的试题
|
||||
|
||||
const handleQuestionRowClick = (row: any) => {
|
||||
selectedQuestion.value = row;
|
||||
};
|
||||
|
||||
const questionList = ref<any[]>([]);
|
||||
|
||||
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数
|
||||
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
const handleChange =async () => {
|
||||
|
||||
if (!selectedQuestion.value) {
|
||||
message.warning('请先选中一个原题');
|
||||
return;
|
||||
}
|
||||
visibleChange.value = true;
|
||||
await getList();
|
||||
};
|
||||
|
||||
const handleSelect =async () => {
|
||||
await handleOpen();
|
||||
};
|
||||
|
||||
|
||||
const handleCancelQue = () => {
|
||||
visibleChange.value = false;
|
||||
list.value=[];
|
||||
selectedSwapQuestion.value = null; // 🔁 清空右侧题目内容
|
||||
selectedChange.value = []; // (可选)清空选中项
|
||||
list.value = [];
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
queryParams.pointNames = '';
|
||||
queryParams.quLevel = '';
|
||||
queryParams.quId = '';
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const resetRandom = () => {
|
||||
|
||||
const originalId = selectedQuestion.value.quId || selectedQuestion.value.id;
|
||||
const sub=selectedQuestion.value.subjectName;
|
||||
console.log('原题 ID:', originalId);
|
||||
console.log('试卷ID:',props.paperId)
|
||||
console.log('原题题型:',sub)
|
||||
|
||||
// 构造参数
|
||||
const payload = {
|
||||
paperId: props.paperId,
|
||||
oldQuId: originalId,
|
||||
subjectName:sub,
|
||||
newQuId: null,
|
||||
};
|
||||
|
||||
|
||||
|
||||
const groupedQuestions = ref({});
|
||||
|
||||
const subjectPriority = {
|
||||
'选择题': 1,
|
||||
'多选题': 2,
|
||||
'判断题': 3,
|
||||
'编程题': 4,
|
||||
'其他': 5
|
||||
};
|
||||
|
||||
const groupBySubjectName = (list) => {
|
||||
const group = {};
|
||||
|
||||
list.forEach((item) => {
|
||||
const subject = item.subjectName || '其他'; // 默认为'其他'
|
||||
if (!group[subject]) {
|
||||
group[subject] = [];
|
||||
}
|
||||
group[subject].push(item);
|
||||
|
||||
try {
|
||||
QuestionApi.changePaperQuRandom( payload); // 根据实际路径修改
|
||||
message.success('换题成功');
|
||||
} catch (error) {
|
||||
console.error('换题请求出错:', error);
|
||||
message.error('请求失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
|
||||
|
||||
const changeTableRef = ref();
|
||||
|
||||
|
||||
|
||||
const handleRowClick = (row: any) => {
|
||||
selectedChange.value = [row]; // 只选中一个换题
|
||||
selectedSwapQuestion.value = row;
|
||||
};
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
queryParams.subjectName=selectedQuestion.value.subjectName
|
||||
try {
|
||||
const data = await QuestionApi.getQuestionlistAnswer(queryParams);
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 打开弹窗时加载题目数据
|
||||
const handleOpen = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getPaperDetailCenterByTaskId(props.paperId);
|
||||
const rawList = res.examQuestionList || [];
|
||||
const schemeList = res.educationPaperSchemeList || [];
|
||||
|
||||
// 构建题型 => sort 数字映射,例如 { '选择题': 1, '文件处理': 2, ... }
|
||||
const sortMap = {};
|
||||
schemeList.forEach((scheme) => {
|
||||
sortMap[scheme.spName] = Number(scheme.sort);
|
||||
});
|
||||
|
||||
groupedQuestions.value = group;
|
||||
|
||||
// 先排序:根据题型的 sort 排序
|
||||
const sortedRawList = [...rawList].sort((a, b) => {
|
||||
return (sortMap[a.subjectName] || 999) - (sortMap[b.subjectName] || 999);
|
||||
});
|
||||
|
||||
// 每种题型的编号计数器
|
||||
const typeCounter = {};
|
||||
let globalIndex = 1;
|
||||
|
||||
questionList.value = sortedRawList.map((q) => {
|
||||
const groupIndex = sortMap[q.subjectName] || 0;
|
||||
typeCounter[groupIndex] = (typeCounter[groupIndex] || 0) + 1;
|
||||
|
||||
const questionIndex = `${groupIndex}.${typeCounter[groupIndex]}`;
|
||||
|
||||
return {
|
||||
...q,
|
||||
index: globalIndex++, // 全局序号 1、2、3…
|
||||
questionIndex, // 按题型分组的编号 1.1、1.2、2.1…
|
||||
};
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleConfirmChange = () => {
|
||||
|
||||
if (selectedChange.value.length !== 1) {
|
||||
message.warning('请选择一个换题');
|
||||
return;
|
||||
}
|
||||
|
||||
const originalId = selectedQuestion.value.quId || selectedQuestion.value.id;
|
||||
const newId = selectedChange.value[0].quId || selectedChange.value[0].id;
|
||||
|
||||
console.log('原题 ID:', originalId);
|
||||
console.log('换题 ID:', newId);
|
||||
console.log('试卷ID:',props.paperId)
|
||||
|
||||
// 构造参数
|
||||
const payload = {
|
||||
paperId: props.paperId,
|
||||
oldQuId: originalId,
|
||||
newQuId: newId,
|
||||
};
|
||||
|
||||
const sortedGroupedQuestions = computed(() => {
|
||||
return Object.keys(groupedQuestions.value)
|
||||
.sort((a, b) => (subjectPriority[a] || subjectPriority['其他']) - (subjectPriority[b] || subjectPriority['其他']))
|
||||
.reduce((acc, subject) => {
|
||||
acc[subject] = groupedQuestions.value[subject];
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
|
||||
const handleOpen = () => {
|
||||
resetFields();
|
||||
questionList.value = [];
|
||||
|
||||
if (props.paperId) {
|
||||
loading.value = true;
|
||||
getPaperDetailByTaskId(props.paperId)
|
||||
.then((res) => {
|
||||
const { educationPaperSchemeList, examQuestionList } = res.data || {};
|
||||
if (Array.isArray(examQuestionList)) {
|
||||
questionList.value = examQuestionList;
|
||||
groupBySubjectName(examQuestionList); // 分类
|
||||
}
|
||||
|
||||
if (Array.isArray(educationPaperSchemeList)) {
|
||||
// 构建以题型名为 key 的映射表(spName 与 scheme 对应)
|
||||
schemeMap.value = educationPaperSchemeList.reduce((acc, item) => {
|
||||
acc[item.spName] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
assignFields(res);
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(`获取试卷详情失败: ${e.message}`);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate?.();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
try {
|
||||
QuestionApi.changePaperQu( payload); // 根据实际路径修改
|
||||
|
||||
message.success('换题成功');
|
||||
|
||||
} catch (error) {
|
||||
console.error('换题请求出错:', error);
|
||||
message.error('请求失败,请稍后重试');
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
visibleChange.value = false;
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
const choiceSubjects = ["选择题", "单选题", "多选题"];
|
||||
const programmingSubjects = ["程序设计"];
|
||||
|
||||
function isChoiceQuestion(subjectName) {
|
||||
return choiceSubjects.some(item => subjectName.includes(item));
|
||||
}
|
||||
|
||||
function isProgrammingQuestion(subjectName) {
|
||||
return programmingSubjects.some(item => subjectName.includes(item));
|
||||
}
|
||||
const isCodingTestQuestion = (subjectName: string): boolean => {
|
||||
return subjectName === '编程题';
|
||||
};
|
||||
|
||||
function optionLabel(index) {
|
||||
return String.fromCharCode(65 + index); // A, B, C, D ...
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -44,13 +44,13 @@
|
||||
style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;"
|
||||
>{{ item.analysis }}</pre>
|
||||
</div>
|
||||
<div
|
||||
<!-- <div
|
||||
v-for="(answer, aIndex) in item.answerList"
|
||||
:key="answer.answerId"
|
||||
class="mb-1"
|
||||
>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}%
|
||||
</div>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 📝 普通题目的选项显示 -->
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
<template>
|
||||
|
||||
<ContentWrap>
|
||||
<el-form
|
||||
class="-mb-15px"
|
||||
:model="queryParams"
|
||||
ref="queryFormRef"
|
||||
:inline="true"
|
||||
label-width="68px"
|
||||
>
|
||||
|
||||
<el-form-item>
|
||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
@click="openForm('create')"
|
||||
v-hasPermi="['system:sms-channel:create']"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" /> 新增</el-button
|
||||
>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ContentWrap>
|
||||
|
||||
<!-- 列表 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list">
|
||||
<el-table-column label="试卷编号" align="center" prop="paperId" />
|
||||
<el-table-column label="使用次数" align="center" prop="counts" />
|
||||
<el-table-column label="抽卷方式" align="center" prop="rollUp">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.quLevel === '0'">固定</span>
|
||||
<span v-else-if="scope.row.quLevel === '1'">AB卷</span>
|
||||
<span v-else-if="scope.row.quLevel === '2'">随机</span>
|
||||
<span v-else-if="scope.row.quLevel === '3'">自选</span>
|
||||
<span v-else>未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="AB卷" align="center" prop="isAb">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.quLevel === '0'">A卷</span>
|
||||
<span v-else-if="scope.row.quLevel === '1'">B卷</span>
|
||||
<span v-else>未知</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否启用" align="center" prop="status" >
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYS_YES_NO" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" align="center">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
@click="openEdit('update', scope.row)"
|
||||
v-hasPermi="['system:sms-channel:update']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
@click="handleDelete(scope.row.id)"
|
||||
v-hasPermi="['system:sms-channel:delete']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</ContentWrap>
|
||||
|
||||
|
||||
|
||||
|
||||
<paper-add v-model="showAdd" :task-Id="taskId" :task-specialty="taskSpecialty" />
|
||||
<paper-edit v-model="showEdit" :data="current" />
|
||||
<!-- <paper-look v-model="showLook" :paper-id="paperId" /> -->
|
||||
<paper-set v-model="showSet" :task-Id="taskId" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
|
||||
import * as SmsChannelApi from '@/api/system/paper';
|
||||
import PaperEdit from './components/step-edit.vue';
|
||||
import PaperAdd from './components/step-add.vue';
|
||||
import PaperLook from './components/step-look.vue';
|
||||
import PaperSet from './components/step-set.vue';
|
||||
import PaperSearch from './components/step-search.vue';
|
||||
import { pagePapers, removePapers, exportPapers } from '@/api/system/paper';
|
||||
|
||||
|
||||
|
||||
defineOptions({ name: 'SystemPaper' });
|
||||
const props = defineProps({
|
||||
taskSpecialty: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
taskId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const { t } = useI18n() // 国际化
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
|
||||
/** 当前编辑数据 */
|
||||
const current = ref<object>();
|
||||
|
||||
/** 是否显示编辑弹窗 */
|
||||
const showEdit = ref(false);
|
||||
|
||||
const showLook = ref(false);
|
||||
|
||||
const showAdd = ref(false);
|
||||
|
||||
const showSet = ref(false);
|
||||
|
||||
|
||||
const smsChannelFormRef = ref()
|
||||
const taskEditRef = ref()
|
||||
const taskAddRef = ref()
|
||||
const taskTempRef = ref()
|
||||
|
||||
|
||||
|
||||
const loading = ref(false) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数据
|
||||
const queryFormRef = ref() // 搜索的表单
|
||||
const queryParams = reactive({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
signature: undefined,
|
||||
status: undefined,
|
||||
createTime: []
|
||||
})
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await SmsChannelApi.pagePapers(queryParams)
|
||||
console.log(res)
|
||||
list.value = res
|
||||
total.value = res.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
/** 重置按钮操作 */
|
||||
const resetQuery = () => {
|
||||
queryFormRef.value.resetFields()
|
||||
handleQuery()
|
||||
}
|
||||
|
||||
/** 添加/修改操作 */
|
||||
const formRef = ref();
|
||||
const openForm = (type: string, id?: number) => {
|
||||
showAdd.value = true
|
||||
taskAddRef.value?.open(type, id)
|
||||
}
|
||||
|
||||
const openEdit = (type: string, row?: object) => {
|
||||
showEdit.value = true
|
||||
current.value = row
|
||||
console.log( current.value )
|
||||
taskEditRef.value?.open(type, row)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (id: number) => {
|
||||
try {
|
||||
// 删除的二次确认
|
||||
await message.delConfirm()
|
||||
// 发起删除
|
||||
await SmsChannelApi.removePaper(id)
|
||||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
/** 初始化 **/
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
</script>
|
||||
@@ -162,6 +162,7 @@
|
||||
v-model="showEdit"
|
||||
:data="current"
|
||||
ref="taskEditRef"
|
||||
:paper-Id="currentPaperId"
|
||||
@done="reload"
|
||||
/>
|
||||
<paper-look
|
||||
@@ -262,7 +263,6 @@ const reload = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
|
||||
const handleSelectionChange = (rows) => {
|
||||
selections.value = rows;
|
||||
}
|
||||
@@ -320,11 +320,15 @@ const openDown = async () => {
|
||||
}
|
||||
};
|
||||
// 打开编辑弹窗
|
||||
const openEdit = (type: string, row?: object) => {
|
||||
const openEdit = (type: string, row?: any)=> {
|
||||
if (row?.counts > 0) {
|
||||
ElMessage.warning('当前试卷使用次数大于0,不允许更换试题');
|
||||
return;
|
||||
}
|
||||
showEdit.value = true;
|
||||
current.value = row;
|
||||
currentPaperId.value = row.paperId ?? null;
|
||||
nextTick(() => {
|
||||
taskEditRef.value?.open(type, row);
|
||||
taskEditRef.value?.open(type, row.paperId);
|
||||
});
|
||||
};
|
||||
const currentPaperId = ref<number | null>(null);
|
||||
|
||||
@@ -127,26 +127,7 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
<el-col :span="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="测评时长" >
|
||||
<el-time-picker
|
||||
v-model="form.examTime"
|
||||
@@ -156,13 +137,31 @@
|
||||
@change="handleFormChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
</el-col>
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="定时检查与学生端联通性,每">
|
||||
<el-input-number v-model="form.isConnect" label="分钟" @change="handleFormChange" />
|
||||
<span>分钟传一次,断联直接交卷</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
|
||||
@@ -1,181 +1,634 @@
|
||||
<template>
|
||||
<Dialog v-model="visible" :title="'编辑试卷'" width="80%" height="10%" @open="handleOpen" center >
|
||||
<ContentWrap>
|
||||
<el-button @click="handleSelect"> <Icon icon="ep:refresh" class="mr-5px" />刷新</el-button>
|
||||
<el-button @click="handleChange"><Icon icon="ep:sort" class="mr-5px" /> 换题</el-button>
|
||||
|
||||
<div style="display: flex; gap: 20px; height: 500px; width: 100%;">
|
||||
<!-- 左侧:题目表格 -->
|
||||
<div style="flex: 3; overflow: auto;">
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="questionList"
|
||||
@row-click="handleQuestionRowClick"
|
||||
highlight-current-row
|
||||
ref="questionTableRef"
|
||||
>
|
||||
<el-table-column label="序号" prop="index" align="center" width="60" />
|
||||
<el-table-column label="编号" prop="questionIndex" align="center" width="80" />
|
||||
<el-table-column label="题型" prop="subjectName" align="center" />
|
||||
<el-table-column label="难度" prop="quLevel" align="center">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.EXAM_QUE_DIFF" :value="scope.row.quLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="知识点" prop="pointNames" align="left" />
|
||||
|
||||
</el-table>
|
||||
</div>
|
||||
<!-- 右侧:题目详情 -->
|
||||
<div style=" flex: 4; border: 1px solid #ebeef5; padding: 16px; border-radius: 4px; overflow: auto; height: 100%;">
|
||||
<template v-if="selectedQuestion">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【题目描述】</div>
|
||||
<div v-html="selectedQuestion.content " style="margin-bottom: 16px;"></div>
|
||||
|
||||
<template v-if="isChoiceQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【选项及答案】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div v-for="(answer, index) in selectedQuestion.answerList" :key="answer.answerId" style="margin-bottom: 4px;">
|
||||
{{ optionLabel(index) }}. {{ answer.content }}
|
||||
<span v-if="answer.isRight === '0'" style="color: green; font-weight: bold">(正确)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
<Dialog v-model="visible" :title="'修改试卷'" width="460" @open="handleOpen" center>
|
||||
|
||||
|
||||
<!-- <div style="margin-bottom: 16px">
|
||||
<strong>试卷ID:</strong>{{ paperId }}
|
||||
</div> -->
|
||||
|
||||
<!-- 滚动区域 -->
|
||||
<div style="max-height: 400px; overflow-y: auto;">
|
||||
<!-- 分组展示题目 -->
|
||||
<div
|
||||
v-for="(items, subject) in sortedGroupedQuestions"
|
||||
:key="subject"
|
||||
>
|
||||
<h4 class="mb-2 text-base text-gray-700 font-medium">
|
||||
{{ subject }}
|
||||
<span v-if="schemeMap[subject]" class="text-sm text-gray-500 font-normal">
|
||||
(每小题 {{ parseFloat(schemeMap[subject].quScores).toFixed(1) }} 分,共 {{ parseFloat(schemeMap[subject].subtotalScore).toFixed(1) }} 分)
|
||||
</span>
|
||||
</h4>
|
||||
|
||||
|
||||
|
||||
<el-card
|
||||
v-for="(item, index) in items"
|
||||
:key="item.quId"
|
||||
class="mb-3"
|
||||
shadow="never"
|
||||
>
|
||||
<div class="text-gray-500 mb-2">题目 {{ index + 1 }}</div>
|
||||
<div v-html="item.content" class="mb-2"></div>
|
||||
|
||||
<!-- 🎯 判断是否为编程题 -->
|
||||
<div v-if="item.subjectName === '编程题'">
|
||||
<!-- 编程题解析 -->
|
||||
<div class="mt-2">
|
||||
<div class="font-bold text-blue-600">解析</div>
|
||||
<pre
|
||||
style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;"
|
||||
>{{ item.analysis }}</pre>
|
||||
<!-- 程序设计题 -->
|
||||
<template v-else-if="isProgrammingQuestion(selectedQuestion.subjectName)">
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div style="margin-bottom: 8px; white-space: pre-wrap;">
|
||||
<strong>【考点语句】:</strong><br />
|
||||
{{ answer.content }}
|
||||
</div>
|
||||
<div
|
||||
v-for="(answer, aIndex) in item.answerList"
|
||||
:key="answer.answerId"
|
||||
class="mb-1"
|
||||
>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}%
|
||||
|
||||
<div v-if="answer.examMysqlKeywordList && answer.examMysqlKeywordList.length">
|
||||
<div style="margin-top: 10px;"><strong>【关键词列表】:</strong></div>
|
||||
<ul style="padding-left: 20px;">
|
||||
<li
|
||||
v-for="keyword in answer.examMysqlKeywordList"
|
||||
:key="keyword.keywordId"
|
||||
style="line-height: 1.6;"
|
||||
>
|
||||
- 关键词:{{ keyword.keyword }},权值:{{ keyword.scoreRate }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 📝 普通题目的选项显示 -->
|
||||
<div v-else-if="item.answerList && item.answerList.length">
|
||||
<div
|
||||
v-for="(answer, aIndex) in item.answerList.slice().sort((a, b) => a.sort - b.sort)"
|
||||
:key="answer.answerId"
|
||||
:style="{
|
||||
color: answer.isRight === '0' ? 'green' : 'inherit',
|
||||
fontWeight: answer.isRight === '0' ? 'bold' : 'normal'
|
||||
}"
|
||||
class="mb-1"
|
||||
>
|
||||
{{ String.fromCharCode(65 + aIndex) }}. {{ answer.content }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无程序设计答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 编程题 -->
|
||||
<template v-else-if="isCodingTestQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【答案】</div>
|
||||
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;">
|
||||
{{ selectedQuestion.analysis }}
|
||||
</pre>
|
||||
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分测试用例】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【输入】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【输出】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无测试用例信息</div>
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分关键字】</div>
|
||||
<div v-if="selectedQuestion.questionKeywords && selectedQuestion.questionKeywords.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.questionKeywords"
|
||||
:key="answer.keywordId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【关键字】:</strong>{{ answer.keyword }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无关键字信息</div>
|
||||
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分标准】</div>
|
||||
<div v-if="selectedQuestion.questionScores">
|
||||
<div
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【检查程序编译】:</strong>{{ selectedQuestion.questionScores.isPass === '0' ? '是' : '否' }} , <strong>【检查程序编译百分比】:</strong>{{ selectedQuestion.questionScores.isPassScore }}%</div>
|
||||
<div><strong>【检查程序结果】:</strong>{{ selectedQuestion.questionScores.isResult === '0' ? '是' : '否' }} ,<strong>【检查程序结果百分比】:</strong>{{ selectedQuestion.questionScores.isResultScore }}%</div>
|
||||
<div><strong>【检查关键字】:</strong>{{ selectedQuestion.questionScores.isKeyword === '0' ? '是' : '否' }} , <strong>【检查关键字百分比】:</strong>{{ selectedQuestion.questionScores.isKeywordScore }}%</div>
|
||||
<div><strong>【使用测试用例】:</strong>{{ selectedQuestion.questionScores.isCompile === '0' ? '是' : '否' }} , <strong>【使用测试用例百分比】:</strong>{{ selectedQuestion.questionScores.isCompileScore }}%</div>
|
||||
|
||||
<div><strong>【关键字得分临界值】:</strong>{{ selectedQuestion.questionScores.keywordCutoff }}%</div>
|
||||
<div><strong>【测试用例得分临界值(小于等于测试用例个数)】:</strong>{{ selectedQuestion.questionScores.compileCutoff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无输入输出信息</div>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<template v-else>
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div v-for="answer in selectedQuestion.answerList" :key="answer.answerId" style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace">
|
||||
<div><strong>【考察点】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【考察类型】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无考点信息</div>
|
||||
</template>
|
||||
|
||||
|
||||
</template>
|
||||
</Dialog>
|
||||
<template v-else>
|
||||
<div style="color: #999;">请点击左侧试题查看详细内容</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model="visibleChange" :title="'换题列表'" width="80%" center @close="handleCancelQue" >
|
||||
<ContentWrap>
|
||||
<div style="display: flex; gap: 20px;height: 500px; width: 100%;">
|
||||
|
||||
<!-- 左侧题目表格 -->
|
||||
<div style="flex: 3;">
|
||||
|
||||
<!-- 搜索条件区域 -->
|
||||
<div style="margin-bottom: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
||||
<el-input
|
||||
v-model="queryParams.pointNames"
|
||||
placeholder="试题知识点"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
/>
|
||||
<el-select
|
||||
v-model="queryParams.quLevel"
|
||||
placeholder="难度"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
>
|
||||
<el-option label="简单" value="0" />
|
||||
<el-option label="一般" value="1" />
|
||||
<el-option label="困难" value="2" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="queryParams.quId"
|
||||
placeholder="题号"
|
||||
clearable
|
||||
style="flex: 1; min-width: 180px;"
|
||||
/>
|
||||
<el-button type="primary" @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />搜索</el-button>
|
||||
<el-button @click="resetSearch"> <Icon icon="ep:refresh" class="mr-5px" />重置</el-button>
|
||||
<el-button @click="resetRandom" > 随机换题</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
ref="changeTableRef"
|
||||
highlight-current-row
|
||||
@row-click="handleRowClick"
|
||||
>
|
||||
|
||||
<el-table-column label="试题编号" align="center" key="id" prop="quId" :show-overflow-tooltip="true"/>
|
||||
<el-table-column label="专业" align="center" prop="specialtyName" width="120" />
|
||||
<el-table-column label="课程" align="center" prop="courseName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="章节名称" align="center" prop="chapteridDictText" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="难度" align="center" prop="quLevel" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.EXAM_QUE_DIFF" :value="scope.row.quLevel" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="知识点" align="center" prop="pointNames" :show-overflow-tooltip="true" />
|
||||
<!-- <el-table-column label="审核状态" align="center" prop="audit" :show-overflow-tooltip="true">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.QUESTION_AUDIT" :value="scope.row.audit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" key="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.SYS_STATUS" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
</el-table>
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 右侧题目详情 -->
|
||||
<div style="flex: 4; border: 1px solid #ebeef5; padding: 16px; border-radius: 4px; overflow-y: auto;">
|
||||
<template v-if="selectedSwapQuestion">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【题目描述】</div>
|
||||
<div v-html="selectedSwapQuestion.content" style="margin-bottom: 16px;"></div>
|
||||
|
||||
<template v-if="isChoiceQuestion(selectedSwapQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【选项及答案】</div>
|
||||
<div v-if="selectedSwapQuestion.answerList && selectedSwapQuestion.answerList.length">
|
||||
<div
|
||||
v-for="(answer, index) in selectedSwapQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 4px;"
|
||||
>
|
||||
{{ optionLabel(index) }}. {{ answer.content }}
|
||||
<span v-if="answer.isRight === '0'" style="color: green; font-weight: bold">(正确)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<!-- 编程题 -->
|
||||
<template v-else-if="isCodingTestQuestion(selectedQuestion.subjectName)">
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【答案】</div>
|
||||
<pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;">
|
||||
{{ selectedQuestion.analysis }}
|
||||
</pre>
|
||||
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分测试用例】</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【输入】:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【输出】:</strong>{{ answer.content }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无测试用例信息</div>
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分关键字】</div>
|
||||
<div v-if="selectedQuestion.questionKeywords && selectedQuestion.questionKeywords.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.questionKeywords"
|
||||
:key="answer.keywordId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【关键字】:</strong>{{ answer.keyword }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无关键字信息</div>
|
||||
|
||||
|
||||
|
||||
<div style="font-weight: bold; margin-bottom: 8px;">【判分标准】</div>
|
||||
<div v-if="selectedQuestion.questionScores">
|
||||
<div
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div><strong>【检查程序编译】:</strong>{{ selectedQuestion.questionScores.isPass === '0' ? '是' : '否' }} , <strong>【检查程序编译百分比】:</strong>{{ selectedQuestion.questionScores.isPassScore }}%</div>
|
||||
<div><strong>【检查程序结果】:</strong>{{ selectedQuestion.questionScores.isResult === '0' ? '是' : '否' }} ,<strong>【检查程序结果百分比】:</strong>{{ selectedQuestion.questionScores.isResultScore }}%</div>
|
||||
<div><strong>【检查关键字】:</strong>{{ selectedQuestion.questionScores.isKeyword === '0' ? '是' : '否' }} , <strong>【检查关键字百分比】:</strong>{{ selectedQuestion.questionScores.isKeywordScore }}%</div>
|
||||
<div><strong>【使用测试用例】:</strong>{{ selectedQuestion.questionScores.isCompile === '0' ? '是' : '否' }} , <strong>【使用测试用例百分比】:</strong>{{ selectedQuestion.questionScores.isCompileScore }}%</div>
|
||||
|
||||
<div><strong>【关键字得分临界值】:</strong>{{ selectedQuestion.questionScores.keywordCutoff }}%</div>
|
||||
<div><strong>【测试用例得分临界值(小于等于测试用例个数)】:</strong>{{ selectedQuestion.questionScores.compileCutoff }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无输入输出信息</div>
|
||||
|
||||
</template>
|
||||
|
||||
<template v-else-if="isProgrammingQuestion(selectedQuestion.subjectName)">
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedQuestion.answerList && selectedQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace;"
|
||||
>
|
||||
<div style="margin-bottom: 8px; white-space: pre-wrap;">
|
||||
<strong>【考点语句】:</strong><br />
|
||||
{{ answer.content }}
|
||||
</div>
|
||||
|
||||
<div v-if="answer.examMysqlKeywordList && answer.examMysqlKeywordList.length">
|
||||
<div style="margin-top: 10px;"><strong>【关键词列表】:</strong></div>
|
||||
<ul style="padding-left: 20px;">
|
||||
<li
|
||||
v-for="keyword in answer.examMysqlKeywordList"
|
||||
:key="keyword.keywordId"
|
||||
style="line-height: 1.6;"
|
||||
>
|
||||
- 关键词:{{ keyword.keyword }},权值:{{ keyword.scoreRate }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无程序设计答案</div>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-else>
|
||||
<div style="display: flex; align-items: center; font-weight: bold; margin-bottom: 8px;">
|
||||
<span>【考点信息】</span>
|
||||
<div style="flex: 1; height: 1px; background: #ccc; margin-left: 10px;"></div>
|
||||
</div>
|
||||
<div v-if="selectedSwapQuestion.answerList && selectedSwapQuestion.answerList.length">
|
||||
<div
|
||||
v-for="answer in selectedSwapQuestion.answerList"
|
||||
:key="answer.answerId"
|
||||
style="margin-bottom: 12px; background: #f5f5f5; padding: 10px; border-radius: 4px; font-family: monospace"
|
||||
>
|
||||
<div><strong>考察点:</strong>{{ answer.content }}</div>
|
||||
<div><strong>考察类型:</strong>{{ answer.contentIn }}</div>
|
||||
<div><strong>【权值】:</strong>{{ answer.scoreRate }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else style="color: #999;">暂无考点信息</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
<div style="color: #999;">请点击左侧题目查看详情</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ContentWrap>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="handleCancelQue">取 消</el-button>
|
||||
<el-button type="primary" @click="handleConfirmChange">确 定</el-button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick, computed } from 'vue';
|
||||
import { useFormData } from '@/utils/use-form-data';
|
||||
import { getPaperDetailByTaskId } from '@/api/system/paper';
|
||||
const message = useMessage() // 消息弹窗
|
||||
|
||||
const props = defineProps({
|
||||
paperId: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(['done']);
|
||||
const visible = defineModel({ type: Boolean });
|
||||
const loading = ref(false);
|
||||
const formRef = ref(null);
|
||||
const questionList = ref([]); // 用于存放试题列表
|
||||
const schemeMap = ref({});
|
||||
|
||||
const [form, resetFields, assignFields] = useFormData({
|
||||
paperId: void 0,
|
||||
taskId: '',
|
||||
counts: '',
|
||||
rollUp: '',
|
||||
isAb: '',
|
||||
status: ''
|
||||
});
|
||||
|
||||
const rules = reactive({});
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
</Dialog>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, nextTick, computed } from 'vue';
|
||||
import { getPaperDetailCenterByTaskId } from '@/api/system/paper';
|
||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||
const message = useMessage(); // 消息弹窗
|
||||
import * as QuestionApi from '@/api/paper/question'
|
||||
const props = defineProps({
|
||||
paperId: String // 接收父组件传来的 paperId
|
||||
});
|
||||
const queryParams = reactive({
|
||||
quLevel: "",
|
||||
pointNames: "",
|
||||
subjectName: "",
|
||||
quId:"",
|
||||
chapteridDictText:"",
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
audit:"0",
|
||||
status:"0",
|
||||
specialtyName:"",
|
||||
courseName:"",
|
||||
})
|
||||
const emit = defineEmits(['done']);
|
||||
const visible = defineModel({ type: Boolean }); // 对应 v-model
|
||||
const visibleChange = defineModel('visibleChange', { type: Boolean }); // 对应 v-model:visibleChange
|
||||
|
||||
const selectedChange = ref<any[]>([]);
|
||||
const selectedSwapQuestion = ref<any>(null); // 换题弹窗中点击的题目
|
||||
const questionTableRef = ref();
|
||||
const selectedQuestion = ref<any>(null); // 第一个弹窗选中的试题
|
||||
|
||||
const handleQuestionRowClick = (row: any) => {
|
||||
selectedQuestion.value = row;
|
||||
};
|
||||
|
||||
const questionList = ref<any[]>([]);
|
||||
|
||||
|
||||
|
||||
const loading = ref(true) // 列表的加载中
|
||||
const total = ref(0) // 列表的总页数
|
||||
const list = ref([]) // 列表的数
|
||||
|
||||
|
||||
const handleCancel = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
const handleChange =async () => {
|
||||
|
||||
if (!selectedQuestion.value) {
|
||||
message.warning('请先选中一个原题');
|
||||
return;
|
||||
}
|
||||
visibleChange.value = true;
|
||||
await getList();
|
||||
};
|
||||
|
||||
const handleSelect =async () => {
|
||||
await handleOpen();
|
||||
};
|
||||
|
||||
|
||||
const handleCancelQue = () => {
|
||||
visibleChange.value = false;
|
||||
list.value=[];
|
||||
selectedSwapQuestion.value = null; // 🔁 清空右侧题目内容
|
||||
selectedChange.value = []; // (可选)清空选中项
|
||||
list.value = [];
|
||||
};
|
||||
|
||||
const resetSearch = () => {
|
||||
queryParams.pointNames = '';
|
||||
queryParams.quLevel = '';
|
||||
queryParams.quId = '';
|
||||
queryParams.pageNo = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const resetRandom = () => {
|
||||
|
||||
const originalId = selectedQuestion.value.quId || selectedQuestion.value.id;
|
||||
const sub=selectedQuestion.value.subjectName;
|
||||
console.log('原题 ID:', originalId);
|
||||
console.log('试卷ID:',props.paperId)
|
||||
console.log('原题题型:',sub)
|
||||
|
||||
// 构造参数
|
||||
const payload = {
|
||||
paperId: props.paperId,
|
||||
oldQuId: originalId,
|
||||
subjectName:sub,
|
||||
newQuId: null,
|
||||
};
|
||||
|
||||
|
||||
|
||||
const groupedQuestions = ref({});
|
||||
|
||||
const subjectPriority = {
|
||||
'选择题': 1,
|
||||
'多选题': 2,
|
||||
'判断题': 3,
|
||||
'编程题': 4,
|
||||
'其他': 5
|
||||
};
|
||||
|
||||
const groupBySubjectName = (list) => {
|
||||
const group = {};
|
||||
|
||||
list.forEach((item) => {
|
||||
const subject = item.subjectName || '其他'; // 默认为'其他'
|
||||
if (!group[subject]) {
|
||||
group[subject] = [];
|
||||
}
|
||||
group[subject].push(item);
|
||||
|
||||
try {
|
||||
QuestionApi.changePaperQuRandom( payload); // 根据实际路径修改
|
||||
message.success('换题成功');
|
||||
} catch (error) {
|
||||
console.error('换题请求出错:', error);
|
||||
message.error('请求失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
getList()
|
||||
}
|
||||
|
||||
|
||||
|
||||
const changeTableRef = ref();
|
||||
|
||||
|
||||
|
||||
const handleRowClick = (row: any) => {
|
||||
selectedChange.value = [row]; // 只选中一个换题
|
||||
selectedSwapQuestion.value = row;
|
||||
};
|
||||
|
||||
/** 查询列表 */
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
queryParams.subjectName=selectedQuestion.value.subjectName
|
||||
try {
|
||||
const data = await QuestionApi.getQuestionlistAnswer(queryParams);
|
||||
list.value = data.list
|
||||
total.value = data.total
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 打开弹窗时加载题目数据
|
||||
const handleOpen = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getPaperDetailCenterByTaskId(props.paperId);
|
||||
const rawList = res.examQuestionList || [];
|
||||
const schemeList = res.educationPaperSchemeList || [];
|
||||
|
||||
// 构建题型 => sort 数字映射,例如 { '选择题': 1, '文件处理': 2, ... }
|
||||
const sortMap = {};
|
||||
schemeList.forEach((scheme) => {
|
||||
sortMap[scheme.spName] = Number(scheme.sort);
|
||||
});
|
||||
|
||||
groupedQuestions.value = group;
|
||||
|
||||
// 先排序:根据题型的 sort 排序
|
||||
const sortedRawList = [...rawList].sort((a, b) => {
|
||||
return (sortMap[a.subjectName] || 999) - (sortMap[b.subjectName] || 999);
|
||||
});
|
||||
|
||||
// 每种题型的编号计数器
|
||||
const typeCounter = {};
|
||||
let globalIndex = 1;
|
||||
|
||||
questionList.value = sortedRawList.map((q) => {
|
||||
const groupIndex = sortMap[q.subjectName] || 0;
|
||||
typeCounter[groupIndex] = (typeCounter[groupIndex] || 0) + 1;
|
||||
|
||||
const questionIndex = `${groupIndex}.${typeCounter[groupIndex]}`;
|
||||
|
||||
return {
|
||||
...q,
|
||||
index: globalIndex++, // 全局序号 1、2、3…
|
||||
questionIndex, // 按题型分组的编号 1.1、1.2、2.1…
|
||||
};
|
||||
});
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleConfirmChange = () => {
|
||||
|
||||
if (selectedChange.value.length !== 1) {
|
||||
message.warning('请选择一个换题');
|
||||
return;
|
||||
}
|
||||
|
||||
const originalId = selectedQuestion.value.quId || selectedQuestion.value.id;
|
||||
const newId = selectedChange.value[0].quId || selectedChange.value[0].id;
|
||||
|
||||
console.log('原题 ID:', originalId);
|
||||
console.log('换题 ID:', newId);
|
||||
console.log('试卷ID:',props.paperId)
|
||||
|
||||
// 构造参数
|
||||
const payload = {
|
||||
paperId: props.paperId,
|
||||
oldQuId: originalId,
|
||||
newQuId: newId,
|
||||
};
|
||||
|
||||
const sortedGroupedQuestions = computed(() => {
|
||||
return Object.keys(groupedQuestions.value)
|
||||
.sort((a, b) => (subjectPriority[a] || subjectPriority['其他']) - (subjectPriority[b] || subjectPriority['其他']))
|
||||
.reduce((acc, subject) => {
|
||||
acc[subject] = groupedQuestions.value[subject];
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
|
||||
const handleOpen = () => {
|
||||
resetFields();
|
||||
questionList.value = [];
|
||||
|
||||
if (props.paperId) {
|
||||
loading.value = true;
|
||||
getPaperDetailByTaskId(props.paperId)
|
||||
.then((res) => {
|
||||
const { educationPaperSchemeList, examQuestionList } = res.data || {};
|
||||
if (Array.isArray(examQuestionList)) {
|
||||
questionList.value = examQuestionList;
|
||||
groupBySubjectName(examQuestionList); // 分类
|
||||
}
|
||||
|
||||
if (Array.isArray(educationPaperSchemeList)) {
|
||||
// 构建以题型名为 key 的映射表(spName 与 scheme 对应)
|
||||
schemeMap.value = educationPaperSchemeList.reduce((acc, item) => {
|
||||
acc[item.spName] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
assignFields(res);
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(`获取试卷详情失败: ${e.message}`);
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate?.();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
try {
|
||||
QuestionApi.changePaperQu( payload); // 根据实际路径修改
|
||||
|
||||
message.success('换题成功');
|
||||
|
||||
} catch (error) {
|
||||
console.error('换题请求出错:', error);
|
||||
message.error('请求失败,请稍后重试');
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
visibleChange.value = false;
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
const choiceSubjects = ["选择题", "单选题", "多选题"];
|
||||
const programmingSubjects = ["程序设计"];
|
||||
|
||||
function isChoiceQuestion(subjectName) {
|
||||
return choiceSubjects.some(item => subjectName.includes(item));
|
||||
}
|
||||
|
||||
function isProgrammingQuestion(subjectName) {
|
||||
return programmingSubjects.some(item => subjectName.includes(item));
|
||||
}
|
||||
const isCodingTestQuestion = (subjectName: string): boolean => {
|
||||
return subjectName === '编程题';
|
||||
};
|
||||
|
||||
function optionLabel(index) {
|
||||
return String.fromCharCode(65 + index); // A, B, C, D ...
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -44,13 +44,13 @@
|
||||
style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto;"
|
||||
>{{ item.analysis }}</pre>
|
||||
</div>
|
||||
<div
|
||||
<!-- <div
|
||||
v-for="(answer, aIndex) in item.answerList"
|
||||
:key="answer.answerId"
|
||||
class="mb-1"
|
||||
>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}%
|
||||
</div>
|
||||
测试用例 {{ aIndex + 1 }}:输入: {{ answer.contentIn }} | 输出: {{ answer.content }} | 占比: {{ answer.scoreRate }}
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 📝 普通题目的选项显示 -->
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
<Icon icon="ep:files" class="mr-5px" />
|
||||
生成笔试试卷
|
||||
</el-button>
|
||||
|
||||
<!-- 批量删除按钮 -->
|
||||
<!-- <el-button
|
||||
type="danger"
|
||||
@@ -81,7 +80,7 @@
|
||||
|
||||
<!-- 列表展示 -->
|
||||
<ContentWrap>
|
||||
<el-table v-loading="loading" :data="list" :row-key="row => row.id" ref="tableRef" @selection-change="handleSelectionChange">
|
||||
<el-table v-loading="loading" :data="list" :row-key="row => row.id" ref="tableRef" @selection-change="handleSelectionChange">
|
||||
|
||||
<!-- 多选列 -->
|
||||
<el-table-column type="selection" width="55" />
|
||||
@@ -163,6 +162,7 @@
|
||||
v-model="showEdit"
|
||||
:data="current"
|
||||
ref="taskEditRef"
|
||||
:paper-Id="currentPaperId"
|
||||
@done="reload"
|
||||
/>
|
||||
<paper-look
|
||||
@@ -263,7 +263,9 @@ const reload = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
|
||||
const handleSelectionChange = (rows) => {
|
||||
selections.value = rows;
|
||||
}
|
||||
// 搜索操作
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1;
|
||||
@@ -290,7 +292,6 @@ const openSet = () => {
|
||||
taskSetRef.value?.open();
|
||||
});
|
||||
};
|
||||
|
||||
/** 表格选中数据 */
|
||||
const selections = ref([]);
|
||||
const downloadLoading = ref(false)
|
||||
@@ -318,21 +319,16 @@ const openDown = async () => {
|
||||
downloadLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
const handleSelectionChange = (rows) => {
|
||||
selections.value = rows;
|
||||
}
|
||||
|
||||
|
||||
// 打开编辑弹窗
|
||||
const openEdit = (type: string, row?: object) => {
|
||||
const openEdit = (type: string, row?: any)=> {
|
||||
if (row?.counts > 0) {
|
||||
ElMessage.warning('当前试卷使用次数大于0,不允许更换试题');
|
||||
return;
|
||||
}
|
||||
showEdit.value = true;
|
||||
current.value = row;
|
||||
currentPaperId.value = row.paperId ?? null;
|
||||
nextTick(() => {
|
||||
taskEditRef.value?.open(type, row);
|
||||
taskEditRef.value?.open(type, row.paperId);
|
||||
});
|
||||
};
|
||||
const currentPaperId = ref<number | null>(null);
|
||||
|
||||
@@ -127,25 +127,6 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
|
||||
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="测评时长" >
|
||||
<el-time-picker
|
||||
@@ -156,13 +137,32 @@
|
||||
@change="handleFormChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
</el-col>
|
||||
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="学生是否可以查看试卷">
|
||||
<el-switch
|
||||
v-model="form.isLook"
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="handleFormChange" />
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="20">
|
||||
|
||||
|
||||
<!-- <el-col :span="12">
|
||||
<el-form-item label="定时检查与学生端联通性,每">
|
||||
<el-input-number v-model="form.isConnect" label="分钟" @change="handleFormChange" />
|
||||
<span>分钟传一次,断联直接交卷</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user