add result panel.

This commit is contained in:
2023-01-10 16:16:14 +08:00
parent a3456595a5
commit e3db109317
22 changed files with 714 additions and 172 deletions

View File

@@ -0,0 +1,277 @@
<script setup lang="ts">
import { useTimeout, useTimeoutFn, useToggle } from "@vueuse/core"
import { TabsPaneContext } from "element-plus"
import party from "party-js"
import { computed, onMounted, ref, watch } from "vue"
import { useRoute } from "vue-router"
import {
SOURCES,
JUDGE_STATUS,
SubmissionStatus,
} from "../../../utils/constants"
import {
submissionMemoryFormat,
submissionTimeFormat,
} from "../../../utils/functions"
import {
LANGUAGE,
Problem,
Submission,
SubmitCodePayload,
} from "../../../utils/types"
import { getSubmission, submissionExists, submitCode } from "../../api"
import SubmissionResultTag from "../../components/submission-result-tag.vue"
interface Props {
state: {
language: LANGUAGE
code: string
}
problem: Problem
}
enum Tab {
testcase = "testcase",
result = "result",
}
const { state, problem } = defineProps<Props>()
const route = useRoute()
const contestID = <string>route.params.contestID || ""
const submission = ref<Submission | null>(null)
const submissionId = ref("")
const tab = ref(Tab.testcase)
const [submitted] = useToggle()
const [tried] = useToggle()
const { start: submitPending, isPending } = useTimeout(5000, {
controls: true,
immediate: false,
})
const { start: fetchSubmission } = useTimeoutFn(
async () => {
const res = await getSubmission(submissionId.value)
submission.value = res.data
const result = submission.value.result
if (
result === SubmissionStatus.judging ||
result === SubmissionStatus.pending
) {
fetchSubmission()
} else {
submitted.value = false
}
},
2000,
{ immediate: false }
)
onMounted(() => {
checkIfTried()
})
async function checkIfTried() {
const res = await submissionExists(problem.id)
tried.value = res.data
}
const judging = computed(
() =>
!!(submission.value && submission.value.result === SubmissionStatus.judging)
)
const pending = computed(
() =>
!!(submission.value && submission.value.result === SubmissionStatus.pending)
)
const submitting = computed(
() =>
!!(
submission.value &&
submission.value.result === SubmissionStatus.submitting
)
)
const submitDisabled = computed(() => {
const code = state.code
if (
code.trim() === "" ||
code === problem.template[state.language] ||
code === SOURCES[state.language]
) {
return true
}
if (judging.value || pending.value || submitting.value) {
return true
}
if (submitted.value) {
return true
}
if (isPending.value) {
return true
}
return false
})
const submitLabel = computed(() => {
if (submitting.value) {
return "正在提交"
}
if (judging.value || pending.value) {
return "正在评分"
}
if (isPending.value) {
return "运行结果"
}
return "点击提交"
})
const msg = computed(() => {
if (
submission.value &&
submission.value.statistic_info &&
submission.value.statistic_info.err_info
) {
return submission.value.statistic_info.err_info
}
const result = submission.value && submission.value.result
if (
result === SubmissionStatus.compile_error ||
result === SubmissionStatus.runtime_error
) {
return "请仔细检查,看看代码格式是不是写错了!"
} else {
return ""
}
})
const infoTable = computed(() => {
if (
submission.value &&
submission.value.result !== SubmissionStatus.accepted &&
submission.value.result !== SubmissionStatus.compile_error &&
submission.value.result !== SubmissionStatus.runtime_error &&
submission.value.info &&
submission.value.info.data &&
submission.value.info.data.length
) {
return submission.value.info.data
} else {
return []
}
})
async function submit() {
const data: SubmitCodePayload = {
problem_id: problem.id,
language: state.language,
code: state.code,
}
if (contestID) {
data.contest_id = parseInt(contestID)
}
submission.value = { result: 9 } as Submission
const res = await submitCode(data)
submissionId.value = res.data.submission_id
// 防止重复提交
submitPending()
submitted.value = true
// 查询结果
fetchSubmission()
}
function onTab(pane: TabsPaneContext) {
if (pane.paneName === Tab.result) {
submit()
}
}
watch(
() => submission.value && submission.value.result,
(result) => {
if (result === SubmissionStatus.accepted) {
party.confetti(document.body, {
count: party.variation.range(200, 400),
size: party.variation.skew(2, 0.3),
})
}
}
)
</script>
<template>
<el-tabs type="border-card" @tab-click="onTab" v-model="tab">
<el-tab-pane label="测试用例" :name="Tab.testcase">
<div class="panel"></div>
</el-tab-pane>
<el-tab-pane :disabled="submitDisabled" :name="Tab.result">
<template #label>
<el-space>
<el-icon>
<i-ep-loading v-if="judging || pending || submitting" />
<i-ep-bell v-else-if="isPending" />
<i-ep-caret-right v-else />
</el-icon>
<span>{{ submitLabel }}</span>
</el-space>
</template>
<div class="panel">
<el-alert
v-if="submission"
:closable="false"
:type="JUDGE_STATUS[submission.result]['type']"
:title="JUDGE_STATUS[submission.result]['name']"
>
</el-alert>
<el-scrollbar
v-if="msg || infoTable.length"
height="280"
class="result"
noresize
>
<div v-if="msg">{{ msg }}</div>
<el-table v-if="infoTable.length" :data="infoTable" stripe>
<el-table-column prop="test_case" label="测试用例" align="center" />
<el-table-column label="测试状态" width="120" align="center">
<template #default="scope">
<SubmissionResultTag
v-if="scope.row"
:result="scope.row.result"
/>
</template>
</el-table-column>
<el-table-column label="占用内存" align="center">
<template #default="scope">
{{ submissionMemoryFormat(scope.row.memory) }}
</template>
</el-table-column>
<el-table-column label="执行耗时" align="center">
<template #default="scope">
{{ submissionTimeFormat(scope.row.real_time) }}
</template>
</el-table-column>
<el-table-column prop="signal" label="信号" align="center" />
</el-table>
</el-scrollbar>
</div>
</el-tab-pane>
</el-tabs>
</template>
<style scoped>
.panel {
height: 320px;
}
.result {
margin-top: 12px;
white-space: pre;
line-height: 1.2;
}
</style>

View File

@@ -1,39 +1,28 @@
<script lang="ts" setup>
import loader, { Monaco } from "@monaco-editor/loader"
import { ref, onBeforeUnmount, onMounted, watch, reactive, computed } from "vue"
import { ref, onBeforeUnmount, onMounted, watch, reactive } from "vue"
import {
LANGUAGE_LABEL,
LANGUAGE_VALUE,
SOURCES,
} from "../../../utils/constants"
import { isMobile } from "../../../utils/breakpoints"
import { submitCode } from "../../api"
import { LANGUAGE, Problem, SubmitCodePayload } from "../../../utils/types"
import { Problem } from "../../../utils/types"
import EditorExec from "./editor-exec.vue"
const { problem, contestID = "" } = defineProps<{
contestID?: string
problemID?: string
problem: Problem
}>()
const { problem } = defineProps<{ problem: Problem }>()
const state = reactive({
values: ref({ ...SOURCES }),
code: SOURCES[problem.languages[0] || "C"],
language: problem.languages[0] || "C",
isMobile,
submissionId: "",
})
const monacoEditorRef = ref()
let monaco: Monaco
function reset() {
state.values[state.language] =
problem.template[state.language] || SOURCES[state.language]
if (monaco && monaco.editor) {
monaco.editor.getModels()[0].setValue(state.values[state.language])
}
}
onMounted(init)
onMounted(() => {
init()
})
onBeforeUnmount(() => {
monaco.editor.getModels().forEach((model) => model.dispose())
@@ -52,13 +41,18 @@ watch(
}
)
async function init() {
state.values[state.language] =
problem.template[state.language] || SOURCES[state.language]
function reset() {
state.code = problem.template[state.language] || SOURCES[state.language]
if (monaco && monaco.editor) {
monaco.editor.getModels()[0].setValue(state.code)
}
}
async function init() {
state.code = problem.template[state.language] || SOURCES[state.language]
monaco = await loader.init()
monaco.editor.create(monacoEditorRef.value, {
value: state.values[state.language], // 编辑器初始显示文字
value: state.code, // 编辑器初始显示文字
language: LANGUAGE_VALUE[state.language],
theme: "vs", // 官方自带三种主题vs, hc-black, or vs-dark
minimap: {
@@ -67,39 +61,17 @@ async function init() {
lineNumbersMinChars: 3,
automaticLayout: true, // 自适应布局
tabSize: 4,
fontSize: state.isMobile ? 16 : 24, // 字体大小
fontSize: state.isMobile ? 20 : 24, // 字体大小
scrollBeyondLastLine: false, // 取消代码后面一大段空白
})
monaco.editor.getModels()[0].onDidChangeContent(() => {
state.values[state.language] = monaco.editor.getModels()[0].getValue()
state.code = monaco.editor.getModels()[0].getValue()
})
}
const submitDisabled = computed(() => {
const code = state.values[state.language]
return (
code.trim() === "" ||
code === problem.template[state.language] ||
code === SOURCES[state.language]
)
})
async function submit() {
const data: SubmitCodePayload = {
problem_id: problem.id,
language: state.language,
code: state.values[state.language],
}
if (contestID) {
data.contest_id = parseInt(contestID)
}
const res = await submitCode(data)
state.submissionId = res.data.submission_id
}
</script>
<template>
<el-form :inline="true">
<el-form inline>
<el-form-item label="语言" label-width="60">
<el-select v-model="state.language" class="language">
<el-option
@@ -115,22 +87,11 @@ async function submit() {
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
<div
<section
ref="monacoEditorRef"
:class="isMobile ? 'editorMobile' : 'editor'"
></div>
<el-tabs type="border-card">
<el-tab-pane label="测试用例"> 1 </el-tab-pane>
<el-tab-pane label="执行结果"> 2 </el-tab-pane>
</el-tabs>
<el-form class="actions">
<el-form-item>
<el-button>运行</el-button>
<el-button type="primary" :disabled="submitDisabled" @click="submit">
提交
</el-button>
</el-form-item>
</el-form>
></section>
<EditorExec :state="state" :problem="problem" />
</template>
<style scoped>
@@ -139,15 +100,11 @@ async function submit() {
}
.editor {
height: 70%;
/* 141px+400 */
height: calc(100vh - 541px);
}
.editorMobile {
height: 500px;
}
.actions {
margin-top: 16px;
float: right;
}
</style>

View File

@@ -13,7 +13,7 @@ const { data: problem, isFinished } = getProblem(problemID)
</script>
<template>
<el-row v-if="isFinished && problem">
<el-row v-if="isFinished && problem" :gutter="20">
<el-col :span="isDesktop ? 12 : 24">
<el-tabs type="border-card">
<el-tab-pane label="题目描述">
@@ -29,17 +29,13 @@ const { data: problem, isFinished } = getProblem(problemID)
<el-tab-pane label="题目信息" lazy>
<ProblemInfo :problem="problem" />
</el-tab-pane>
<el-tab-pane label="提交情况">3</el-tab-pane>
<el-tab-pane label="提交列表">3</el-tab-pane>
</el-tabs>
</el-col>
<el-col v-if="isDesktop" :span="12" class="editorWrapper">
<el-col v-if="isDesktop" :span="12">
<Editor :problem="problem" />
</el-col>
</el-row>
</template>
<style scoped>
.editorWrapper {
height: calc(100vh - 171px);
}
</style>
<style scoped></style>

View File

@@ -100,7 +100,7 @@ onMounted(listProblems)
</script>
<template>
<el-form :inline="true">
<el-form inline>
<el-form-item label="难度">
<el-select v-model="query.difficulty">
<el-option