评阅页直接运行学生的程序;离开页面时结束正在运行的程序
CI/CD / test (push) Canceled after 0s
CI/CD / deploy (push) Canceled after 0s

- 运行区(窗体桌面、提示框、虚拟实训台、编译错误和控制台输出)抽成 RunArea 组件,实验页和评阅页共用
- 评阅页代码区加"运行"标签页:运行学生提交的代码,可以操作窗体和实训台;换学生时自动停止并清空;
  点编译错误跳到对应代码;原来的按钮改为"在新标签页运行"
- 运行会话加 close():离开实验页、评阅页时断开连接,服务器随即结束程序(之前要等 60 分钟超时)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 20:12:31 +08:00
1 parent 91237b85b8
commit 8a92354fe4
4 files changed
+215 -113

No files matched your search

+6 -101
View File
@@ -13,12 +13,10 @@ import CodeEditor from '../editor/CodeEditor.vue'
import { api } from '../api'
import { currentUser, logout } from '../auth'
import { deleteWork, loadWork, sampleWorkKey, saveWork } from '../editor/works'
import RunArea from '../run/RunArea.vue'
import { createRunSession } from '../run/session'
import GradeResultView from '../grading/GradeResultView.vue'
import { captureForms } from '../grading/screenshots'
import TrainingBoard from '../scene/TrainingBoard.vue'
import FormWindow from '../winforms/FormWindow.vue'
import MessageBoxDialog from '../winforms/MessageBoxDialog.vue'
const props = defineProps<{ assignmentId?: string; submissionId?: string }>()
@@ -91,7 +89,10 @@ function onKeyDown(event: KeyboardEvent) {
}
onMounted(() => window.addEventListener('keydown', onKeyDown, { capture: true }))
onBeforeUnmount(() => window.removeEventListener('keydown', onKeyDown, { capture: true }))
onBeforeUnmount(() => {
window.removeEventListener('keydown', onKeyDown, { capture: true })
session.close()
})
onMounted(async () => {
// 一打开页面就连接服务器,实训台在运行程序之前就能显示和调节
@@ -473,40 +474,7 @@ function run() {
/>
</section>
<section class="run-area">
<div class="desktop">
<FormWindow
v-for="(form, index) in state.forms"
:key="form.root.id"
:form="form.root"
:modal="form.modal"
:offset="index"
:act="session.act"
/>
<MessageBoxDialog
v-for="box in state.messageBoxes"
:key="box.requestId"
:box="box"
@reply="session.replyMessageBox(box.requestId, $event)"
/>
<div v-if="state.forms.length === 0" class="desktop-hint">点击"运行"后,学生程序的窗体会显示在这里</div>
</div>
<TrainingBoard :board="state.board" @set="session.setBoard" @reset="session.resetBoard" />
<div class="output">
<div
v-for="(d, i) in state.diagnostics"
:key="`d${i}`"
:class="['diag', d.severity]"
title="点击跳到出错的代码"
@click="showDiagnostic(d.file, d.line, d.column)"
>
{{ d.file }}({{ d.line }},{{ d.column }}) {{ d.code }}:{{ d.message }}
<div v-if="d.hint" class="hint">提示:{{ d.hint }}</div>
</div>
<pre v-for="(e, i) in state.errors" :key="`e${i}`" class="error">{{ e }}</pre>
<pre v-if="state.console" class="console">{{ state.console }}</pre>
</div>
</section>
<RunArea :session="session" @diagnostic="showDiagnostic" />
</main>
</div>
</template>
@@ -623,67 +591,4 @@ function run() {
.new-form-error {
color: #dc2626;
}
.run-area {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px;
min-width: 0;
overflow: auto;
}
.desktop {
position: relative;
flex: 1;
min-height: 320px;
background: #2d6aa3;
border-radius: 4px;
/* 窗体比桌面区域大时可以滚动查看 */
overflow: auto;
}
.desktop-hint {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
color: rgb(255 255 255 / 0.7);
}
.output {
max-height: 180px;
overflow: auto;
font-size: 12px;
}
.diag {
cursor: pointer;
}
.diag:hover {
text-decoration: underline;
}
.diag.error,
.error {
color: #b91c1c;
}
.diag.warning {
color: #a16207;
}
.hint {
color: #2563eb;
padding-left: 1em;
}
.console {
background: #111827;
color: #e5e7eb;
padding: 6px;
margin: 0;
}
</style>