diff --git a/package.json b/package.json index 1e703ee..bd4a637 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@wangeditor/editor": "^5.1.23", "@wangeditor/editor-for-vue": "^5.1.10", "@zxcvbn-ts/core": "^3.0.4", + "ace-builds": "^1.43.2", "animate.css": "^4.1.1", "axios": "^1.6.8", "benz-amr-recorder": "^1.1.5", @@ -76,6 +77,7 @@ "vue-i18n": "9.10.2", "vue-router": "4.4.5", "vue-types": "^5.1.1", + "vue3-ace-editor": "^2.2.4", "vue3-signature": "^0.2.4", "vuedraggable": "^4.1.0", "web-storage-cache": "^1.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3e047e..694e8a7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: '@zxcvbn-ts/core': specifier: ^3.0.4 version: 3.0.4 + ace-builds: + specifier: ^1.43.2 + version: 1.43.2 animate.css: specifier: ^4.1.1 version: 4.1.1 @@ -161,6 +164,9 @@ importers: vue-types: specifier: ^5.1.1 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: specifier: ^0.2.4 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} hasBin: true - ace-builds@1.39.1: - resolution: {integrity: sha512-HcJbBzx8qY66t9gZo/sQu7pi0wO/CFLdYn1LxQO1WQTfIkMfyc7LRnBpsp/oNCSSU/LL83jXHN1fqyOTuIhUjg==} + ace-builds@1.43.2: + resolution: {integrity: sha512-3wzJUJX0RpMc03jo0V8Q3bSb/cKPnS7Nqqw8fVHsCCHweKMiTIxT3fP46EhjmVy6MCuxwP801ere+RW245phGw==} acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, tarball: https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz} @@ -5079,6 +5085,12 @@ packages: vue: 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: resolution: {integrity: sha512-XFwwFVK9OG3F085pKIq2SlNVqx32WdFH+TXbGEWc5FfEKpx8oMmZuAwZZ50K/pH2FgmJSE8IRwU9DDhrLpd6iA==} peerDependencies: @@ -7659,7 +7671,7 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 - ace-builds@1.39.1: {} + ace-builds@1.43.2: {} acorn-jsx@5.3.2(acorn@8.14.0): dependencies: @@ -9124,7 +9136,7 @@ snapshots: jsoneditor@9.10.5: dependencies: - ace-builds: 1.39.1 + ace-builds: 1.43.2 ajv: 6.12.6 javascript-natural-sort: 0.7.1 jmespath: 0.16.0 @@ -10546,6 +10558,12 @@ snapshots: optionalDependencies: 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)): dependencies: default-passive-events: 2.0.0 diff --git a/src/views/paper/question/CdesignForm.vue b/src/views/paper/question/CdesignForm.vue index cade925..8bbc060 100644 --- a/src/views/paper/question/CdesignForm.vue +++ b/src/views/paper/question/CdesignForm.vue @@ -42,7 +42,7 @@ 不能采用类似 scanf("请输入: %s") 的形式 插入作答区标记后请在试题题目中明确告知学生不要自行更改作答区标记,以免无法正常评分。 - +
更改答题程序模板无效,若需要更改请上传程序文件。
@@ -154,11 +154,15 @@ 不能采用类似 scanf("请输入: %s") 的形式

- +
- 添加为关键字 - 上传程序文件 - 运行并测试 + 添加为关键字 + 上传程序文件 + 运行并测试
-
-
测试用例
-
-
- -
- 编译得分比例: - - - +
- +
@@ -456,6 +513,89 @@ 取 消 + + + + + + + + + + 数字 + 空白 + 单词字符 + 任意字符 + + + + + 测试 + {{ regexTestResult }} + + + + 添加 + + + + {{ tag }} + + + + + + + +
+
+ 调试运行 + 评分报告 +
+
+
运行结果:
+
{{ runResult }}
+
+
+ +
+ + + + +
+

总得分: {{ scoringReport.totalScore }}

+

编译结果: {{ scoringReport.compileResult }}

+ +
+
+

暂无评分报告。

+
+ +
@@ -464,7 +604,7 @@ import * as QuestionApi from '@/api/paper/question' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' 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 * as SpecialtyApi from '@/api/points' 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(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 = () => { inputVisible.value = true nextTick(() => { @@ -589,7 +773,7 @@ const formData = ref({ ] }) const formRef = ref() // 表单 Ref - +const textareaRef = ref() /** 添加/修改操作 */ const FileRef = ref() const openForm = (type: string) => { @@ -630,10 +814,12 @@ const handleUploadSuccess = async ({ url, fileType, file }) => { break } } + // 处理参考答案的上传 + if (fileType === '4') { + textarea.value = res.data + } } -/** 页面内容结束 */ - /** 打开弹窗 */ const open = async (queryParams: any, type: string, id?: number) => { dialogVisible.value = true @@ -672,11 +858,26 @@ const open = async (queryParams: any, type: string, id?: number) => { defineExpose({ open }) // 提供 open 方法,用于打开弹窗 const programData = ref({ - title: '', - checkAutoScore: false, - checkKeyword: false, - percent: 0, - radio1: '' + 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 }) const examList = ref([] as any) @@ -693,8 +894,100 @@ const multipleScoreSelection = ref([] as any) const handleScoreSelectionChange = (val: any) => { multipleScoreSelection.value = val } -const delScore = (val: any) => { - console.log(val) + +// 编辑关键字弹窗相关 +const scoreDialogVisible = ref(false) +const currentScoreItem = ref({}) +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: '' } ] } + + // 重置 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 = '' textarea.value = '' inputText.value = '' diff --git a/src/views/paper/question/components/CodeEditor.vue b/src/views/paper/question/components/CodeEditor.vue new file mode 100644 index 0000000..4ff0835 --- /dev/null +++ b/src/views/paper/question/components/CodeEditor.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/views/paper/question/components/FileForm.vue b/src/views/paper/question/components/FileForm.vue index 3bad2e3..0eb87cb 100644 --- a/src/views/paper/question/components/FileForm.vue +++ b/src/views/paper/question/components/FileForm.vue @@ -80,7 +80,7 @@ const submitFormSuccess = (response: any, file: any) => { dialogVisible.value = false formLoading.value = false uploadRef.value?.clearFiles() - message.success(t('common.createSuccess')) + // message.success(t('common.createSuccess')) // 触发成功事件,携带文件信息传递给父组件 emit('success', { url, fileType: currentType.value, file: file.raw })