feat(frontend): show submission performance in problem workbench

This commit is contained in:
2026-10-09 14:45:53 +08:00
parent 62132324da
commit 2ad35865c0
+173 -63
View File
@@ -2,13 +2,14 @@
import { computed, onBeforeUnmount, ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { findProblem, discussions, submissions } from '../data/preview'
import { findProblem, discussions, submissions, type Submission } from '../data/preview'
import { useCodeDraft, codeTemplates, languageLabels } from '../composables/useCodeDraft'
import CodeEditor from '../components/CodeEditor.vue'
import DifficultyTag from '../components/DifficultyTag.vue'
import VerdictTag from '../components/VerdictTag.vue'
import EmptyState from '../components/EmptyState.vue'
import UiIcon from '../components/UiIcon.vue'
import SubmissionPerformance from '../components/SubmissionPerformance.vue'
const route = useRoute()
const id = String(route.params.id)
const problem = computed(() => findProblem(id))
@@ -17,12 +18,20 @@ const theme = ref('vs')
const fontSize = ref(14)
const tab = ref('description')
const testTab = ref('input')
const workspaceTab = ref<'code' | 'submission'>('code')
const selectedSubmission = ref<Submission>()
const testInput = ref(problem.value?.sampleInput ?? '')
const settingsOpen = ref(false)
const split = ref(50)
const workspace = ref<HTMLDivElement>()
const codeRelated = computed(() => submissions.filter((s) => s.problemId === id))
const relatedDiscussion = computed(() => discussions.filter((d) => d.problemId === id))
function viewSubmission(submissionId: string) {
const submission = codeRelated.value.find((item) => item.id === submissionId)
if (!submission) return
selectedSubmission.value = submission
workspaceTab.value = 'submission'
}
const draftText = computed(
() =>
({
@@ -160,10 +169,14 @@ async function copySample(value: string) {
/><el-table-column label="结果" width="100"
><template #default="{ row }"
><VerdictTag :value="row.verdict" /></template></el-table-column
><el-table-column label="详情" width="80"
><el-table-column label="用时" width="90"
><template #default="{ row }">{{
row.time === null ? '—' : row.time + ' ms'
}}</template> </el-table-column
><el-table-column label="详情" width="105"
><template #default="{ row }"
><RouterLink :to="'/submissions/' + row.id" class="text-link"
>查看</RouterLink
><el-button link type="primary" @click="viewSubmission(row.id)"
>查看表现</el-button
></template
></el-table-column
></el-table
@@ -210,67 +223,106 @@ async function copySample(value: string) {
@keydown.right.prevent="split = Math.min(70, split + 2)"
/>
<section class="panel editor-panel">
<header class="editor-toolbar">
<span class="small muted">语言</span
><el-select v-model="language" aria-label="编程语言" size="small"
><el-option
v-for="(label, value) in languageLabels"
:key="value"
:label="label"
:value="value" /></el-select
><el-tooltip content="重置模板"
><el-button size="small" aria-label="重置代码模板" @click="resetCode"
><UiIcon name="refresh" /></el-button></el-tooltip
><span class="draft-label" aria-live="polite">{{ draftText }}</span
><el-button size="small" aria-label="下载代码" @click="downloadCode"
><UiIcon name="document" /></el-button
><el-popover v-model:visible="settingsOpen" trigger="click" width="240"
><template #reference
><el-button size="small" aria-label="编辑器设置"><UiIcon name="settings" /></el-button
></template>
<div class="editor-settings">
<label>主题</label
><el-select v-model="theme" aria-label="编辑器主题"
><el-option label="浅色" value="vs" /><el-option
label="深色"
value="vs-dark" /></el-select
><label>字号</label><el-slider v-model="fontSize" :min="12" :max="22" show-input /></div
></el-popover>
</header>
<div class="editor-area">
<CodeEditor v-model="code" :language="language" :theme="theme" :font-size="fontSize" />
</div>
<div class="editor-testing">
<el-tabs v-model="testTab"
><el-tab-pane label="自测输入" name="input"
><el-input
v-model="testInput"
type="textarea"
:rows="3"
resize="none"
placeholder="输入自测数据"
aria-label="自测输入"
/><el-button
v-if="problem.sampleInput"
link
type="primary"
size="small"
@click="testInput = problem.sampleInput"
>填入题目样例</el-button
></el-tab-pane
><el-tab-pane label="运行结果" name="result"
><p class="test-result-placeholder">
尚未运行。判题服务接入后展示标准输出、错误信息与资源使用情况。
</p></el-tab-pane
></el-tabs
<header class="workspace-view-tabs" aria-label="工作台视图">
<button
type="button"
:aria-pressed="workspaceTab === 'code'"
@click="workspaceTab = 'code'"
>
<UiIcon name="document" />代码编辑器
</button>
<button
type="button"
:disabled="!selectedSubmission"
:aria-pressed="workspaceTab === 'submission'"
@click="workspaceTab = 'submission'"
>
<UiIcon name="rankings" />{{
selectedSubmission ? '提交 #' + selectedSubmission.id : '评测详情'
}}
</button>
</header>
<div v-show="workspaceTab === 'code'" class="editor-workspace">
<header class="editor-toolbar">
<span class="small muted">语言</span
><el-select v-model="language" aria-label="编程语言" size="small"
><el-option
v-for="(label, value) in languageLabels"
:key="value"
:label="label"
:value="value" /></el-select
><el-tooltip content="重置模板"
><el-button size="small" aria-label="重置代码模板" @click="resetCode"
><UiIcon name="refresh" /></el-button></el-tooltip
><span class="draft-label" aria-live="polite">{{ draftText }}</span
><el-button size="small" aria-label="下载代码" @click="downloadCode"
><UiIcon name="document" /></el-button
><el-popover v-model:visible="settingsOpen" trigger="click" width="240"
><template #reference
><el-button size="small" aria-label="编辑器设置"><UiIcon name="settings" /></el-button
></template>
<div class="editor-settings">
<label>主题</label
><el-select v-model="theme" aria-label="编辑器主题"
><el-option label="浅色" value="vs" /><el-option
label="深色"
value="vs-dark" /></el-select
><label>字号</label
><el-slider v-model="fontSize" :min="12" :max="22" show-input /></div
></el-popover>
</header>
<div class="editor-area">
<CodeEditor v-model="code" :language="language" :theme="theme" :font-size="fontSize" />
</div>
<div class="editor-testing">
<el-tabs v-model="testTab"
><el-tab-pane label="自测输入" name="input"
><el-input
v-model="testInput"
type="textarea"
:rows="3"
resize="none"
placeholder="输入自测数据"
aria-label="自测输入"
/><el-button
v-if="problem.sampleInput"
link
type="primary"
size="small"
@click="testInput = problem.sampleInput"
>填入题目样例</el-button
></el-tab-pane
><el-tab-pane label="运行结果" name="result"
><p class="test-result-placeholder">
尚未运行。判题服务接入后展示标准输出、错误信息与资源使用情况。
</p></el-tab-pane
></el-tabs
>
</div>
<footer class="editor-buttons">
<span class="small muted">评测服务待接入</span
><el-button size="small" @click="save">保存草稿</el-button
><el-button size="small" type="success" plain disabled>自测运行</el-button
><el-button size="small" type="primary" disabled>提交代码</el-button>
</footer>
</div>
<div
v-if="selectedSubmission"
v-show="workspaceTab === 'submission'"
class="submission-workspace"
>
<header class="workspace-submission-heading">
<div>
<VerdictTag :value="selectedSubmission.verdict" full />
<p>{{ selectedSubmission.username }} · {{ selectedSubmission.created }}</p>
</div>
<RouterLink :to="'/submissions/' + selectedSubmission.id" class="text-link"
>完整详情 →</RouterLink
>
</header>
<SubmissionPerformance :submission="selectedSubmission" />
<p class="notice-strip">当前展示示例评测记录,源码与测试点详情待接入。</p>
</div>
<footer class="editor-buttons">
<span class="small muted">评测服务待接入</span
><el-button size="small" @click="save">保存草稿</el-button
><el-button size="small" type="success" plain disabled>自测运行</el-button
><el-button size="small" type="primary" disabled>提交代码</el-button>
</footer>
</section>
</div>
<EmptyState v-else title="题目不存在"
@@ -299,6 +351,64 @@ async function copySample(value: string) {
min-height: 0;
border-radius: 14px;
}
.editor-workspace {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
}
.workspace-view-tabs {
display: flex;
gap: 8px;
padding: 10px 14px;
border-bottom: 1px solid #edf0f5;
}
.workspace-view-tabs button {
display: flex;
align-items: center;
gap: 7px;
padding: 8px 12px;
border: 0;
border-radius: 7px;
background: transparent;
color: #8396af;
font-size: 12px;
}
.workspace-view-tabs button[aria-pressed='true'] {
color: var(--blue);
background: #edf6ff;
}
.workspace-view-tabs button:disabled {
color: #aab6c5;
}
.workspace-view-tabs button:focus-visible {
outline: 2px solid var(--blue);
}
.submission-workspace {
flex: 1;
min-height: 0;
overflow: auto;
padding: 24px;
}
.workspace-submission-heading {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 20px;
}
.workspace-submission-heading p {
margin-top: 8px;
font-size: 11px;
color: #93a4ba;
}
.workspace-submission-heading > a {
font-size: 12px;
}
.submission-workspace .performance-distribution {
padding: 20px;
}
.statement-tabs {
padding: 8px 20px 0;
background: #fff;