fix: 添加C语言出题界面判分设置前端显示逻辑
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
"@zxcvbn-ts/core": "^3.0.4",
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
|
"ace-builds": "^1.43.2",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
"benz-amr-recorder": "^1.1.5",
|
"benz-amr-recorder": "^1.1.5",
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
"vue-i18n": "9.10.2",
|
"vue-i18n": "9.10.2",
|
||||||
"vue-router": "4.4.5",
|
"vue-router": "4.4.5",
|
||||||
"vue-types": "^5.1.1",
|
"vue-types": "^5.1.1",
|
||||||
|
"vue3-ace-editor": "^2.2.4",
|
||||||
"vue3-signature": "^0.2.4",
|
"vue3-signature": "^0.2.4",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
"web-storage-cache": "^1.1.1",
|
"web-storage-cache": "^1.1.1",
|
||||||
|
26
pnpm-lock.yaml
generated
26
pnpm-lock.yaml
generated
@@ -38,6 +38,9 @@ importers:
|
|||||||
'@zxcvbn-ts/core':
|
'@zxcvbn-ts/core':
|
||||||
specifier: ^3.0.4
|
specifier: ^3.0.4
|
||||||
version: 3.0.4
|
version: 3.0.4
|
||||||
|
ace-builds:
|
||||||
|
specifier: ^1.43.2
|
||||||
|
version: 1.43.2
|
||||||
animate.css:
|
animate.css:
|
||||||
specifier: ^4.1.1
|
specifier: ^4.1.1
|
||||||
version: 4.1.1
|
version: 4.1.1
|
||||||
@@ -161,6 +164,9 @@ importers:
|
|||||||
vue-types:
|
vue-types:
|
||||||
specifier: ^5.1.1
|
specifier: ^5.1.1
|
||||||
version: 5.1.3(vue@3.5.12(typescript@5.3.3))
|
version: 5.1.3(vue@3.5.12(typescript@5.3.3))
|
||||||
|
vue3-ace-editor:
|
||||||
|
specifier: ^2.2.4
|
||||||
|
version: 2.2.4(ace-builds@1.43.2)(vue@3.5.12(typescript@5.3.3))
|
||||||
vue3-signature:
|
vue3-signature:
|
||||||
specifier: ^0.2.4
|
specifier: ^0.2.4
|
||||||
version: 0.2.4(vue@3.5.12(typescript@5.3.3))
|
version: 0.2.4(vue@3.5.12(typescript@5.3.3))
|
||||||
@@ -2340,8 +2346,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==, tarball: https://registry.npmmirror.com/JSONStream/-/JSONStream-1.3.5.tgz}
|
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==, tarball: https://registry.npmmirror.com/JSONStream/-/JSONStream-1.3.5.tgz}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
ace-builds@1.39.1:
|
ace-builds@1.43.2:
|
||||||
resolution: {integrity: sha512-HcJbBzx8qY66t9gZo/sQu7pi0wO/CFLdYn1LxQO1WQTfIkMfyc7LRnBpsp/oNCSSU/LL83jXHN1fqyOTuIhUjg==}
|
resolution: {integrity: sha512-3wzJUJX0RpMc03jo0V8Q3bSb/cKPnS7Nqqw8fVHsCCHweKMiTIxT3fP46EhjmVy6MCuxwP801ere+RW245phGw==}
|
||||||
|
|
||||||
acorn-jsx@5.3.2:
|
acorn-jsx@5.3.2:
|
||||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, tarball: https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz}
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, tarball: https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz}
|
||||||
@@ -5079,6 +5085,12 @@ packages:
|
|||||||
vue:
|
vue:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
vue3-ace-editor@2.2.4:
|
||||||
|
resolution: {integrity: sha512-FZkEyfpbH068BwjhMyNROxfEI8135Sc+x8ouxkMdCNkuj/Tuw83VP/gStFQqZHqljyX9/VfMTCdTqtOnJZGN8g==}
|
||||||
|
peerDependencies:
|
||||||
|
ace-builds: '*'
|
||||||
|
vue: ^3
|
||||||
|
|
||||||
vue3-signature@0.2.4:
|
vue3-signature@0.2.4:
|
||||||
resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==}
|
resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -7659,7 +7671,7 @@ snapshots:
|
|||||||
jsonparse: 1.3.1
|
jsonparse: 1.3.1
|
||||||
through: 2.3.8
|
through: 2.3.8
|
||||||
|
|
||||||
ace-builds@1.39.1: {}
|
ace-builds@1.43.2: {}
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.14.0):
|
acorn-jsx@5.3.2(acorn@8.14.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9124,7 +9136,7 @@ snapshots:
|
|||||||
|
|
||||||
jsoneditor@9.10.5:
|
jsoneditor@9.10.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
ace-builds: 1.39.1
|
ace-builds: 1.43.2
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
javascript-natural-sort: 0.7.1
|
javascript-natural-sort: 0.7.1
|
||||||
jmespath: 0.16.0
|
jmespath: 0.16.0
|
||||||
@@ -10546,6 +10558,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vue: 3.5.12(typescript@5.3.3)
|
vue: 3.5.12(typescript@5.3.3)
|
||||||
|
|
||||||
|
vue3-ace-editor@2.2.4(ace-builds@1.43.2)(vue@3.5.12(typescript@5.3.3)):
|
||||||
|
dependencies:
|
||||||
|
ace-builds: 1.43.2
|
||||||
|
resize-observer-polyfill: 1.5.1
|
||||||
|
vue: 3.5.12(typescript@5.3.3)
|
||||||
|
|
||||||
vue3-signature@0.2.4(vue@3.5.12(typescript@5.3.3)):
|
vue3-signature@0.2.4(vue@3.5.12(typescript@5.3.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
default-passive-events: 2.0.0
|
default-passive-events: 2.0.0
|
||||||
|
@@ -42,7 +42,7 @@
|
|||||||
不能采用类似 scanf("请输入: %s") 的形式
|
不能采用类似 scanf("请输入: %s") 的形式
|
||||||
插入作答区标记后请在试题题目中明确告知学生不要自行更改作答区标记,以免无法正常评分。
|
插入作答区标记后请在试题题目中明确告知学生不要自行更改作答区标记,以免无法正常评分。
|
||||||
</div>
|
</div>
|
||||||
<ElTextarea v-model="formData.answer" :rows="4" />
|
<CodeEditor v-model="formData.answer" :rows="8" />
|
||||||
<div class="tips"> 更改答题程序模板无效,若需要更改请上传程序文件。 </div>
|
<div class="tips"> 更改答题程序模板无效,若需要更改请上传程序文件。 </div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<ElButton type="primary" plain @click="openForm('1')">
|
<ElButton type="primary" plain @click="openForm('1')">
|
||||||
@@ -154,11 +154,15 @@
|
|||||||
不能采用类似 scanf("请输入: %s") 的形式</p
|
不能采用类似 scanf("请输入: %s") 的形式</p
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<ElTextarea v-model="textarea" :rows="4" />
|
<CodeEditor ref="textareaRef" v-model="textarea" :rows="10" />
|
||||||
<div class="btn-line">
|
<div class="btn-line">
|
||||||
<el-button type="primary" plain>添加为关键字</el-button>
|
<el-button type="primary" plain @click="addKeywordFromSelection"
|
||||||
<el-button type="primary" plain>上传程序文件</el-button>
|
>添加为关键字</el-button
|
||||||
<el-button type="primary" plain>运行并测试</el-button>
|
>
|
||||||
|
<el-button type="primary" plain @click="openForm('4')">上传程序文件</el-button>
|
||||||
|
<el-button type="primary" plain @click="openRunTestDialog"
|
||||||
|
>运行并测试</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="programData.checkAutoScore"
|
v-model="programData.checkAutoScore"
|
||||||
@@ -167,44 +171,47 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="programData.checkAutoScore">
|
||||||
<div class="line">
|
<div class="line">
|
||||||
<div class="title-text">测试用例</div>
|
<div class="title-text">测试用例</div>
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="programData.checkKeyword"
|
v-model="programData.checkCompile"
|
||||||
label="检查代码关键字"
|
label="编译通过得分"
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
|
<template v-if="programData.checkCompile">
|
||||||
<div class="flex" style="margin-left: 20px">
|
<div class="flex" style="margin-left: 20px">
|
||||||
编译得分比例:
|
编译得分比例:
|
||||||
<el-input
|
<el-input
|
||||||
v-model="programData.percent"
|
v-model="programData.compilePercent"
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="Please input"
|
placeholder="Please input"
|
||||||
>
|
>
|
||||||
<template #append>%</template>
|
<template #append>%</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="programData.checkKeyword"
|
v-model="programData.useTestCases"
|
||||||
label="使用测试用例"
|
label="使用测试用例"
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div v-if="programData.useTestCases">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
v-model="programData.checkKeyword"
|
v-model="programData.fullScoreOnAllTestCases"
|
||||||
label="测试用例全对时直接得满分s"
|
label="测试用例全对时直接得满分"
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="tip">
|
<div class="tip">
|
||||||
<p
|
<p
|
||||||
>判分时,测试用例得分比例 = 100% - 关键字得分比例 - 编译得分比例;默认最小
|
>判分时,测试用例得分比例 = 100% - 关键字得分比例 -
|
||||||
10% 得分比例;<br />
|
编译得分比例;默认最小 10% 得分比例;<br />
|
||||||
设置“测试用例全对时直接得满分”,测试用例结果全对时,忽略关键字得分,直接获得试题满分;否则按上述条件计算得分。</p
|
设置“测试用例全对时直接得满分”,测试用例结果全对时,忽略关键字得分,直接获得试题满分;否则按上述条件计算得分。</p
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -256,6 +263,7 @@
|
|||||||
label="检查代码关键字"
|
label="检查代码关键字"
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
|
<template v-if="programData.checkKeyword">
|
||||||
<div class="flex" style="margin-left: 20px">
|
<div class="flex" style="margin-left: 20px">
|
||||||
关键字得分比例:
|
关键字得分比例:
|
||||||
<el-input
|
<el-input
|
||||||
@@ -276,7 +284,9 @@
|
|||||||
<template #append>%</template>
|
<template #append>%</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
<template v-if="programData.checkKeyword">
|
||||||
<div class="tip" style="margin-top: 10px">
|
<div class="tip" style="margin-top: 10px">
|
||||||
<p
|
<p
|
||||||
>提示:关键字支持正则表达式匹配方式,在新建或编辑中设置。<br />
|
>提示:关键字支持正则表达式匹配方式,在新建或编辑中设置。<br />
|
||||||
@@ -308,10 +318,56 @@
|
|||||||
<el-table-column prop="displayIndex" label="权重(%)" />
|
<el-table-column prop="displayIndex" label="权重(%)" />
|
||||||
<el-table-column label="操作">
|
<el-table-column label="操作">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button type="primary" @click="delScore(scope)">删除</el-button>
|
<el-button type="primary" @click="editScore(scope)">编辑</el-button>
|
||||||
|
<el-button type="danger" @click="delScore(scope)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="line">
|
||||||
|
<div class="title-text">自动判分选项</div>
|
||||||
|
<div class="block">
|
||||||
|
<el-form label-width="120px" label-position="right">
|
||||||
|
<el-form-item label="输出读取方式:">
|
||||||
|
<el-radio-group v-model="programData.outputReadMethod">
|
||||||
|
<el-radio label="1">自动捕获程序输出</el-radio>
|
||||||
|
<el-radio label="2">从文件中读取输出</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
<div
|
||||||
|
v-if="programData.outputReadMethod === '2'"
|
||||||
|
class="flex"
|
||||||
|
style="margin-left: 10px"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="programData.outputFileName"
|
||||||
|
placeholder="请输入文件名"
|
||||||
|
style="width: 200px"
|
||||||
|
maxlength="20"
|
||||||
|
/>
|
||||||
|
<span style="margin-left: 10px; color: #999"
|
||||||
|
>注:文件名最长20个字符。</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="输出匹配规则:">
|
||||||
|
<el-radio-group v-model="programData.outputMatchRule">
|
||||||
|
<el-radio label="1">完全匹配</el-radio>
|
||||||
|
<el-radio label="2">包含匹配</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="匹配选项:">
|
||||||
|
<el-checkbox v-model="programData.ignoreCase" label="忽略输出大小写" />
|
||||||
|
<el-checkbox v-model="programData.ignoreSymbols"
|
||||||
|
>忽略输出中英文符号(认为半角符号和全角中文符号是一样的)</el-checkbox
|
||||||
|
>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="programData.ignoreSpaces"
|
||||||
|
label="忽略输出中每行内容中的全部空格"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="line">
|
<div class="line">
|
||||||
@@ -346,10 +402,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="试题解析" name="analyse">
|
<el-tab-pane label="试题解析" name="analyse">
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<ElTextarea v-model="textarea" :rows="4" />
|
<CodeEditor v-model="formData.analysis" :rows="10" />
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane name="keyword">
|
<el-tab-pane name="keyword">
|
||||||
@@ -456,6 +513,89 @@
|
|||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- 编辑关键字弹窗 -->
|
||||||
|
<el-dialog v-model="scoreDialogVisible" title="编辑关键字" width="600px">
|
||||||
|
<el-form :model="currentScoreItem" label-width="100px">
|
||||||
|
<el-form-item label="关键字">
|
||||||
|
<el-input v-model="currentScoreItem.title" placeholder="请输入关键字或正则表达式" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="插入正则">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button @click="insertRegexMark('\\d')">数字</el-button>
|
||||||
|
<el-button @click="insertRegexMark('\\s')">空白</el-button>
|
||||||
|
<el-button @click="insertRegexMark('\\w')">单词字符</el-button>
|
||||||
|
<el-button @click="insertRegexMark('.')">任意字符</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="测试正则">
|
||||||
|
<el-input
|
||||||
|
v-model="regexTestInput"
|
||||||
|
placeholder="输入测试字符串"
|
||||||
|
style="width: 200px; margin-right: 10px"
|
||||||
|
/>
|
||||||
|
<el-button @click="testRegex">测试</el-button>
|
||||||
|
<span style="margin-left: 10px">{{ regexTestResult }}</span>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="等价关键字">
|
||||||
|
<el-input
|
||||||
|
v-model="equivalentKeywordInput"
|
||||||
|
placeholder="输入后按回车或点击添加"
|
||||||
|
style="width: 200px; margin-right: 10px"
|
||||||
|
@keyup.enter="addEquivalentKeyword"
|
||||||
|
/>
|
||||||
|
<el-button @click="addEquivalentKeyword">添加</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="">
|
||||||
|
<el-tag
|
||||||
|
v-for="tag in currentScoreItem.equivalentKeywords"
|
||||||
|
:key="tag"
|
||||||
|
closable
|
||||||
|
:disable-transitions="false"
|
||||||
|
@close="removeEquivalentKeyword(tag)"
|
||||||
|
style="margin-right: 5px"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</el-tag>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="scoreDialogVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="confirmScoreEdit">确 定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
<!-- 运行并测试弹窗 -->
|
||||||
|
<el-dialog v-model="runTestDialogVisible" title="运行与测试" width="700px">
|
||||||
|
<div class="run-test-dialog">
|
||||||
|
<div class="button-group">
|
||||||
|
<el-button type="primary" @click="runCode" :loading="isCodeRunning">调试运行</el-button>
|
||||||
|
<el-button type="success" @click="openReportDialog">评分报告</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="result-panel">
|
||||||
|
<div class="result-title">运行结果:</div>
|
||||||
|
<pre class="result-content">{{ runResult }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="runTestDialogVisible = false">关 闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 评分报告弹窗 -->
|
||||||
|
<el-dialog v-model="reportDialogVisible" title="评分报告" width="800px" append-to-body>
|
||||||
|
<!-- 在这里填充评分报告的具体内容 -->
|
||||||
|
<div v-if="scoringReport">
|
||||||
|
<p><strong>总得分:</strong> {{ scoringReport.totalScore }}</p>
|
||||||
|
<p><strong>编译结果:</strong> {{ scoringReport.compileResult }}</p>
|
||||||
|
<!-- ... 其他报告项 -->
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<p>暂无评分报告。</p>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="reportDialogVisible = false">关 闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<FileForm ref="FileRef" @success="handleUploadSuccess" />
|
<FileForm ref="FileRef" @success="handleUploadSuccess" />
|
||||||
@@ -464,7 +604,7 @@
|
|||||||
import * as QuestionApi from '@/api/paper/question'
|
import * as QuestionApi from '@/api/paper/question'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import ElTextarea from './components/el-textarea.vue'
|
import CodeEditor from './components/CodeEditor.vue'
|
||||||
import { defaultProps, handleTree } from '@/utils/tree'
|
import { defaultProps, handleTree } from '@/utils/tree'
|
||||||
import * as SpecialtyApi from '@/api/points'
|
import * as SpecialtyApi from '@/api/points'
|
||||||
import * as DeptApi from '@/api/system/dept'
|
import * as DeptApi from '@/api/system/dept'
|
||||||
@@ -529,6 +669,50 @@ const handleCloseTag = (url: string, type: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 运行与测试相关
|
||||||
|
const runTestDialogVisible = ref(false)
|
||||||
|
const reportDialogVisible = ref(false)
|
||||||
|
const isCodeRunning = ref(false)
|
||||||
|
const runResult = ref('')
|
||||||
|
const scoringReport = ref<any>(null) // 存储评分报告
|
||||||
|
|
||||||
|
const openRunTestDialog = () => {
|
||||||
|
runResult.value = '点击“调试运行”按钮以执行代码。'
|
||||||
|
runTestDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const runCode = async () => {
|
||||||
|
if (!textarea.value) {
|
||||||
|
message.warning('参考答案代码为空,无法运行。')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isCodeRunning.value = true
|
||||||
|
runResult.value = '代码正在运行中,请稍候...'
|
||||||
|
try {
|
||||||
|
// 假设有一个API用于运行代码
|
||||||
|
// const res = await QuestionApi.runCode({ code: textarea.value })
|
||||||
|
// runResult.value = res.data.output || '代码运行完成,无输出。'
|
||||||
|
} catch (error) {
|
||||||
|
runResult.value = '代码运行出错,请检查代码或联系管理员。'
|
||||||
|
console.error('Run code error:', error)
|
||||||
|
} finally {
|
||||||
|
isCodeRunning.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const openReportDialog = async () => {
|
||||||
|
// 假设有一个API用于获取评分报告
|
||||||
|
// const report = await QuestionApi.getScoringReport({ questionId: formData.value.id });
|
||||||
|
// scoringReport.value = report.data;
|
||||||
|
|
||||||
|
// 模拟数据
|
||||||
|
scoringReport.value = {
|
||||||
|
totalScore: 85,
|
||||||
|
compileResult: '编译成功'
|
||||||
|
}
|
||||||
|
reportDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
const showInput = () => {
|
const showInput = () => {
|
||||||
inputVisible.value = true
|
inputVisible.value = true
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
@@ -589,7 +773,7 @@ const formData = ref({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
|
const textareaRef = ref<any>()
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const FileRef = ref()
|
const FileRef = ref()
|
||||||
const openForm = (type: string) => {
|
const openForm = (type: string) => {
|
||||||
@@ -630,10 +814,12 @@ const handleUploadSuccess = async ({ url, fileType, file }) => {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 处理参考答案的上传
|
||||||
|
if (fileType === '4') {
|
||||||
|
textarea.value = res.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 页面内容结束 */
|
|
||||||
|
|
||||||
/** 打开弹窗 */
|
/** 打开弹窗 */
|
||||||
const open = async (queryParams: any, type: string, id?: number) => {
|
const open = async (queryParams: any, type: string, id?: number) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
@@ -672,11 +858,26 @@ const open = async (queryParams: any, type: string, id?: number) => {
|
|||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
const programData = ref({
|
const programData = ref({
|
||||||
title: '',
|
title: '', // 测试程序标题
|
||||||
checkAutoScore: false,
|
checkAutoScore: false, // 控制“自动判分方式”复选框
|
||||||
checkKeyword: false,
|
checkCompile: false, // 控制“编译通过得分”复选框
|
||||||
percent: 0,
|
compilePercent: 0, // 编译得分比例
|
||||||
radio1: ''
|
useTestCases: false, // 控制“使用测试用例”复选框
|
||||||
|
fullScoreOnAllTestCases: false, // 控制“全对得满分”复选框
|
||||||
|
checkKeyword: false, // 控制“检查代码关键字”复选框
|
||||||
|
percent: 0, // 关键字得分比例
|
||||||
|
radio1: '', // 控制测试用例部分对或全对的单选框
|
||||||
|
// 自动判分选项
|
||||||
|
outputReadMethod: '1',
|
||||||
|
outputFileName: '',
|
||||||
|
outputMatchRule: '1',
|
||||||
|
ignoreCase: false,
|
||||||
|
ignoreSymbols: false,
|
||||||
|
ignoreSpaces: false,
|
||||||
|
// 其它选项
|
||||||
|
timeLimit: 1000,
|
||||||
|
memoryLimit: 128,
|
||||||
|
codeLengthLimit: 64
|
||||||
})
|
})
|
||||||
|
|
||||||
const examList = ref([] as any)
|
const examList = ref([] as any)
|
||||||
@@ -693,8 +894,100 @@ const multipleScoreSelection = ref([] as any)
|
|||||||
const handleScoreSelectionChange = (val: any) => {
|
const handleScoreSelectionChange = (val: any) => {
|
||||||
multipleScoreSelection.value = val
|
multipleScoreSelection.value = val
|
||||||
}
|
}
|
||||||
const delScore = (val: any) => {
|
|
||||||
console.log(val)
|
// 编辑关键字弹窗相关
|
||||||
|
const scoreDialogVisible = ref(false)
|
||||||
|
const currentScoreItem = ref<any>({})
|
||||||
|
const equivalentKeywordInput = ref('')
|
||||||
|
const regexTestInput = ref('')
|
||||||
|
const regexTestResult = ref('')
|
||||||
|
|
||||||
|
const editScore = (scope: any) => {
|
||||||
|
// 创建一个深拷贝,避免直接修改原始数据
|
||||||
|
currentScoreItem.value = JSON.parse(JSON.stringify(scope.row))
|
||||||
|
// 确保等价关键字列表存在
|
||||||
|
if (!currentScoreItem.value.equivalentKeywords) {
|
||||||
|
currentScoreItem.value.equivalentKeywords = []
|
||||||
|
}
|
||||||
|
regexTestInput.value = ''
|
||||||
|
regexTestResult.value = ''
|
||||||
|
scoreDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmScoreEdit = () => {
|
||||||
|
const index = scoreKeyList.value.findIndex((item) => item.title === currentScoreItem.value.title) // 假设title是唯一标识
|
||||||
|
if (index !== -1) {
|
||||||
|
scoreKeyList.value[index] = currentScoreItem.value
|
||||||
|
}
|
||||||
|
scoreDialogVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const addEquivalentKeyword = () => {
|
||||||
|
if (
|
||||||
|
equivalentKeywordInput.value &&
|
||||||
|
!currentScoreItem.value.equivalentKeywords.includes(equivalentKeywordInput.value)
|
||||||
|
) {
|
||||||
|
currentScoreItem.value.equivalentKeywords.push(equivalentKeywordInput.value)
|
||||||
|
equivalentKeywordInput.value = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeEquivalentKeyword = (keyword: string) => {
|
||||||
|
const index = currentScoreItem.value.equivalentKeywords.indexOf(keyword)
|
||||||
|
if (index > -1) {
|
||||||
|
currentScoreItem.value.equivalentKeywords.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const insertRegexMark = (mark: string) => {
|
||||||
|
currentScoreItem.value.title += mark
|
||||||
|
}
|
||||||
|
|
||||||
|
const testRegex = () => {
|
||||||
|
if (!currentScoreItem.value.title) {
|
||||||
|
regexTestResult.value = '请输入正则表达式'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const regex = new RegExp(currentScoreItem.value.title)
|
||||||
|
if (regex.test(regexTestInput.value)) {
|
||||||
|
regexTestResult.value = '匹配成功'
|
||||||
|
} else {
|
||||||
|
regexTestResult.value = '匹配失败'
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
regexTestResult.value = `正则表达式错误: ${e.message}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const delScore = async (scope: any) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.confirm('是否确认删除该关键字?')
|
||||||
|
// 从列表中移除
|
||||||
|
scoreKeyList.value.splice(scope.$index, 1)
|
||||||
|
message.success('删除成功')
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addKeywordFromSelection = () => {
|
||||||
|
if (!textareaRef.value) return
|
||||||
|
|
||||||
|
const selectedText = textareaRef.value.getSelectedText().trim()
|
||||||
|
|
||||||
|
if (selectedText) {
|
||||||
|
// 检查关键字是否已存在
|
||||||
|
if (scoreKeyList.value.some((item) => item.title === selectedText)) {
|
||||||
|
message.warning('关键字已存在')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scoreKeyList.value.push({
|
||||||
|
title: selectedText,
|
||||||
|
displayIndex: 0 // 默认权重为0,可以根据需要修改
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
message.warning('请先在测试程序中选择要添加的关键字')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关键字
|
// 关键字
|
||||||
@@ -791,6 +1084,28 @@ const resetForm = () => {
|
|||||||
{ quId: '', url: '', fileType: '2', fileName: '' }
|
{ quId: '', url: '', fileType: '2', fileName: '' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重置 programData
|
||||||
|
programData.value = {
|
||||||
|
title: '',
|
||||||
|
checkAutoScore: false,
|
||||||
|
checkCompile: false,
|
||||||
|
compilePercent: 0,
|
||||||
|
useTestCases: false,
|
||||||
|
fullScoreOnAllTestCases: false,
|
||||||
|
checkKeyword: false,
|
||||||
|
percent: 0,
|
||||||
|
radio1: '',
|
||||||
|
outputReadMethod: '1',
|
||||||
|
outputFileName: '',
|
||||||
|
outputMatchRule: '1',
|
||||||
|
ignoreCase: false,
|
||||||
|
ignoreSymbols: false,
|
||||||
|
ignoreSpaces: false,
|
||||||
|
timeLimit: 1000,
|
||||||
|
memoryLimit: 128,
|
||||||
|
codeLengthLimit: 64
|
||||||
|
}
|
||||||
content.value = ''
|
content.value = ''
|
||||||
textarea.value = ''
|
textarea.value = ''
|
||||||
inputText.value = ''
|
inputText.value = ''
|
||||||
|
89
src/views/paper/question/components/CodeEditor.vue
Normal file
89
src/views/paper/question/components/CodeEditor.vue
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<v-ace-editor
|
||||||
|
v-model:value="content"
|
||||||
|
@init="editorInit"
|
||||||
|
lang="c_cpp"
|
||||||
|
theme="tomorrow_night"
|
||||||
|
:style="{ height: editorHeight }"
|
||||||
|
:options="{
|
||||||
|
useWorker: true,
|
||||||
|
enableBasicAutocompletion: true,
|
||||||
|
enableSnippets: true,
|
||||||
|
enableLiveAutocompletion: true,
|
||||||
|
fontSize: 14,
|
||||||
|
showPrintMargin: false,
|
||||||
|
highlightActiveLine: true,
|
||||||
|
showGutter: true
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed, defineEmits, defineProps, defineExpose } from 'vue'
|
||||||
|
import { VAceEditor } from 'vue3-ace-editor'
|
||||||
|
// 引入需要的语言模式、主题和扩展
|
||||||
|
import 'ace-builds/src-noconflict/mode-c_cpp'
|
||||||
|
import 'ace-builds/src-noconflict/theme-tomorrow_night'
|
||||||
|
import 'ace-builds/src-noconflict/ext-language_tools'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
rows: {
|
||||||
|
type: Number,
|
||||||
|
default: 8 // 默认行数
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
const content = ref(props.modelValue)
|
||||||
|
let editorInstance: any = null
|
||||||
|
|
||||||
|
// 根据行数计算编辑器高度
|
||||||
|
const editorHeight = computed(() => {
|
||||||
|
// 假设每行大约 21px
|
||||||
|
return `${props.rows * 21}px`
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newValue) => {
|
||||||
|
if (content.value !== newValue) {
|
||||||
|
content.value = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(content, (newValue) => {
|
||||||
|
emit('update:modelValue', newValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
const editorInit = (editor) => {
|
||||||
|
editorInstance = editor
|
||||||
|
editor.container.style.lineHeight = 1.5
|
||||||
|
editor.renderer.updateFontSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义获取选中内容的方法
|
||||||
|
const getSelectedText = () => {
|
||||||
|
if (editorInstance) {
|
||||||
|
return editorInstance.getSelectedText()
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 向父组件暴露方法
|
||||||
|
defineExpose({
|
||||||
|
getSelectedText
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.ace_editor {
|
||||||
|
border: 1px solid #444;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -80,7 +80,7 @@ const submitFormSuccess = (response: any, file: any) => {
|
|||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
uploadRef.value?.clearFiles()
|
uploadRef.value?.clearFiles()
|
||||||
message.success(t('common.createSuccess'))
|
// message.success(t('common.createSuccess'))
|
||||||
|
|
||||||
// 触发成功事件,携带文件信息传递给父组件
|
// 触发成功事件,携带文件信息传递给父组件
|
||||||
emit('success', { url, fileType: currentType.value, file: file.raw })
|
emit('success', { url, fileType: currentType.value, file: file.raw })
|
||||||
|
Reference in New Issue
Block a user