refactor.

This commit is contained in:
2023-01-10 21:46:43 +08:00
parent e0c21ab3a9
commit 3dafc00e4a
11 changed files with 336 additions and 300 deletions

View File

@@ -2,9 +2,10 @@
import { JUDGE_STATUS } from "../../utils/constants" import { JUDGE_STATUS } from "../../utils/constants"
import { SUBMISSION_RESULT } from "../../utils/types" import { SUBMISSION_RESULT } from "../../utils/types"
const { result } = defineProps<{ interface Props {
result: SUBMISSION_RESULT result: SUBMISSION_RESULT
}>() }
defineProps<Props>()
</script> </script>
<template> <template>

View File

@@ -1,279 +1,41 @@
<script setup lang="ts"> <script setup lang="ts">
import { useTimeout, useTimeoutFn, useToggle } from "@vueuse/core" import { useToggle } from "@vueuse/core"
import { TabsPaneContext } from "element-plus" import { TabsPaneContext } from "element-plus"
import party from "party-js" import { inject, onMounted, Ref, ref } from "vue"
import { computed, onMounted, ref, watch } from "vue" import { Problem } from "../../../utils/types"
import { useRoute } from "vue-router" import { submissionExists } from "../../api"
import { import SubmitPanel from "./submit-panel.vue"
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" const tab = ref("testcase")
const submitPanelRef = ref<{ submit: Function }>()
interface Props { const problem = inject<Ref<Problem>>("problem")
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 [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(() => { onMounted(() => {
checkIfTried() checkIfTried()
}) })
async function checkIfTried() { async function checkIfTried() {
const res = await submissionExists(problem.id) const res = await submissionExists(problem!.value.id)
tried.value = res.data 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) { function onTab(pane: TabsPaneContext) {
if (pane.paneName === Tab.result) { if (pane.paneName === "result") {
submit() submitPanelRef && submitPanelRef.value!.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> </script>
<template> <template>
<el-tabs type="border-card" @tab-click="onTab" v-model="tab"> <el-tabs type="border-card" @tab-click="onTab" v-model="tab">
<el-tab-pane label="测试用例" :name="Tab.testcase"> <el-tab-pane label="测试用例" name="testcase">
<div class="panel"> <div class="panel">
<el-table height="320"></el-table> <el-table height="320"></el-table>
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :disabled="submitDisabled" :name="Tab.result"> <SubmitPanel ref="submitPanelRef" />
<template #label>
<el-space :size="2">
<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]['alertType']"
: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> </el-tabs>
</template> </template>
<style scoped> <style scoped></style>
.panel {
height: 320px;
}
.result {
margin-top: 12px;
white-space: pre;
line-height: 1.2;
}
</style>

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import loader, { Monaco } from "@monaco-editor/loader" import loader, { Monaco } from "@monaco-editor/loader"
import { ref, onBeforeUnmount, onMounted, watch, reactive } from "vue" import { ref, onBeforeUnmount, onMounted, watch, reactive, provide } from "vue"
import { import {
LANGUAGE_LABEL, LANGUAGE_LABEL,
LANGUAGE_VALUE, LANGUAGE_VALUE,
@@ -10,12 +10,17 @@ import { isMobile } from "../../../utils/breakpoints"
import { Problem } from "../../../utils/types" import { Problem } from "../../../utils/types"
import EditorExec from "./editor-exec.vue" import EditorExec from "./editor-exec.vue"
const { problem } = defineProps<{ problem: Problem }>() interface Props {
const state = reactive({ problem: Problem
code: SOURCES[problem.languages[0] || "C"], }
language: problem.languages[0] || "C",
isMobile, const props = defineProps<Props>()
const code = reactive({
value: SOURCES[props.problem.languages[0] || "C"],
language: props.problem.languages[0] || "C",
}) })
provide("code", code)
const monacoEditorRef = ref() const monacoEditorRef = ref()
let monaco: Monaco let monaco: Monaco
@@ -29,12 +34,12 @@ onBeforeUnmount(() => {
}) })
watch( watch(
() => state.language, () => code.language,
() => { () => {
if (monaco && monaco.editor) { if (monaco && monaco.editor) {
monaco.editor.setModelLanguage( monaco.editor.setModelLanguage(
monaco.editor.getModels()[0], monaco.editor.getModels()[0],
LANGUAGE_VALUE[state.language] LANGUAGE_VALUE[code.language]
) )
reset() reset()
} }
@@ -42,30 +47,30 @@ watch(
) )
function reset() { function reset() {
state.code = problem.template[state.language] || SOURCES[state.language] code.value = props.problem.template[code.language] || SOURCES[code.language]
if (monaco && monaco.editor) { if (monaco && monaco.editor) {
monaco.editor.getModels()[0].setValue(state.code) monaco.editor.getModels()[0].setValue(code.value)
} }
} }
async function init() { async function init() {
state.code = problem.template[state.language] || SOURCES[state.language] code.value = props.problem.template[code.language] || SOURCES[code.language]
monaco = await loader.init() monaco = await loader.init()
monaco.editor.create(monacoEditorRef.value, { monaco.editor.create(monacoEditorRef.value, {
value: state.code, // 编辑器初始显示文字 value: code.value, // 编辑器初始显示文字
language: LANGUAGE_VALUE[state.language], language: LANGUAGE_VALUE[code.language],
theme: "vs", // 官方自带三种主题vs, hc-black, or vs-dark theme: "vs-dark", // 官方自带三种主题vs, hc-black, or vs-dark
minimap: { minimap: {
enabled: false, enabled: false,
}, },
lineNumbersMinChars: 3, lineNumbersMinChars: 3,
automaticLayout: true, // 自适应布局 automaticLayout: true, // 自适应布局
tabSize: 4, tabSize: 4,
fontSize: state.isMobile ? 20 : 24, // 字体大小 fontSize: isMobile.value ? 20 : 24, // 字体大小
scrollBeyondLastLine: false, // 取消代码后面一大段空白 scrollBeyondLastLine: false, // 取消代码后面一大段空白
}) })
monaco.editor.getModels()[0].onDidChangeContent(() => { monaco.editor.getModels()[0].onDidChangeContent(() => {
state.code = monaco.editor.getModels()[0].getValue() code.value = monaco.editor.getModels()[0].getValue()
}) })
} }
</script> </script>
@@ -73,7 +78,7 @@ async function init() {
<template> <template>
<el-form inline> <el-form inline>
<el-form-item label="语言" label-width="60"> <el-form-item label="语言" label-width="60">
<el-select v-model="state.language" class="language"> <el-select v-model="code.language" class="language">
<el-option <el-option
v-for="item in problem.languages" v-for="item in problem.languages"
:key="item" :key="item"
@@ -91,7 +96,7 @@ async function init() {
ref="monacoEditorRef" ref="monacoEditorRef"
:class="isMobile ? 'editorMobile' : 'editor'" :class="isMobile ? 'editorMobile' : 'editor'"
></section> ></section>
<EditorExec :state="state" :problem="problem" /> <EditorExec />
</template> </template>
<style scoped> <style scoped>

View File

@@ -1,7 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { Problem } from "../../../utils/types" import { Problem } from "../../../utils/types"
const { problem } = defineProps<{ problem: Problem }>() interface Props {
problem: Problem
}
defineProps<Props>()
</script> </script>
<template> <template>

View File

@@ -4,7 +4,10 @@ import { getACRate, getTagColor, parseTime } from "../../../utils/functions"
import { isDesktop } from "../../../utils/breakpoints" import { isDesktop } from "../../../utils/breakpoints"
import { Problem } from "../../../utils/types" import { Problem } from "../../../utils/types"
const { problem } = defineProps<{ problem: Problem }>() interface Props {
problem: Problem
}
defineProps<Props>()
</script> </script>
<template> <template>

View File

@@ -0,0 +1,243 @@
<script setup lang="ts">
import { useTimeout, useTimeoutFn, useToggle } from "@vueuse/core"
import { computed, inject, Ref, ref, watch } from "vue"
import party from "party-js"
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, submitCode } from "../../api"
import SubmissionResultTag from "../../components/submission-result-tag.vue"
const code = inject<{ value: string; language: LANGUAGE }>("code", {
value: "",
language: "C",
})
const problem = inject<Ref<Problem>>("problem")
const route = useRoute()
const contestID = <string>route.params.contestID || ""
const submissionId = ref("")
const submission = ref<Submission | null>(null)
const [submitted] = 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 }
)
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 value = code.value
if (
value.trim() === "" ||
value === problem!.value.template[code.language] ||
value === SOURCES[code.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!.value.id,
language: code.language,
code: code.value,
}
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()
}
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),
})
}
}
)
defineExpose({ submit })
</script>
<template>
<el-tab-pane :disabled="submitDisabled" name="result">
<template #label>
<el-space :size="2">
<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]['alertType']"
: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>
</template>
<style scoped>
.panel {
height: 320px;
}
.result {
margin-top: 12px;
white-space: pre;
line-height: 1.5;
}
</style>

View File

@@ -4,12 +4,19 @@ import ProblemContent from "./components/problem-content.vue"
import ProblemInfo from "./components/problem-info.vue" import ProblemInfo from "./components/problem-info.vue"
import { getProblem } from "../api" import { getProblem } from "../api"
import { isDesktop, isMobile } from "../../utils/breakpoints" import { isDesktop, isMobile } from "../../utils/breakpoints"
import { provide, readonly } from "vue"
const { problemID = "", contestID = "" } = defineProps<{ interface Props {
problemID?: string problemID: string
contestID?: string contestID?: string
}>() }
const { data: problem, isFinished } = getProblem(problemID)
const props = withDefaults(defineProps<Props>(), {
contestID: "",
})
const { data: problem, isFinished } = getProblem(props.problemID)
provide("problem", readonly(problem))
</script> </script>
<template> <template>
@@ -25,7 +32,7 @@ const { data: problem, isFinished } = getProblem(problemID)
<el-tab-pane v-if="isMobile" label="代码编辑" lazy> <el-tab-pane v-if="isMobile" label="代码编辑" lazy>
<Editor :problem="problem" /> <Editor :problem="problem" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="比赛信息" v-if="contestID" lazy></el-tab-pane> <el-tab-pane label="比赛信息" v-if="props.contestID" lazy></el-tab-pane>
<el-tab-pane label="题目信息" lazy> <el-tab-pane label="题目信息" lazy>
<ProblemInfo :problem="problem" /> <ProblemInfo :problem="problem" />
</el-tab-pane> </el-tab-pane>

View File

@@ -36,30 +36,32 @@
import Resizer from "./resizer.vue" import Resizer from "./resizer.vue"
import Pane from "./pane.vue" import Pane from "./pane.vue"
import { computed, ref } from "vue" import { computed, ref } from "vue"
import { classNameToArray } from "element-plus/es/utils"
const { interface Props {
minPercent = 10,
defaultPercent = 50,
split,
className,
} = defineProps<{
minPercent?: number minPercent?: number
defaultPercent?: number defaultPercent?: number
split: "vertical" | "horizontal" split: "vertical" | "horizontal"
className?: string className?: string
}>() }
const props = withDefaults(defineProps<Props>(), {
minPercent: 10,
defaultPercent: 50,
split: "horizontal",
className: "",
})
const emit = defineEmits(["resize"]) const emit = defineEmits(["resize"])
const active = ref(false) const active = ref(false)
const hasMoved = ref(false) const hasMoved = ref(false)
const percent = ref(defaultPercent) const percent = ref(props.defaultPercent)
const type = ref(split === "vertical" ? "width" : "height") const type = ref(props.split === "vertical" ? "width" : "height")
const resizeType = ref(split === "vertical" ? "left" : "top") const resizeType = ref(props.split === "vertical" ? "left" : "top")
const userSelect = computed(() => (active.value ? "none" : "auto")) const userSelect = computed(() => (active.value ? "none" : "auto"))
const cursor = computed(() => const cursor = computed(() =>
active.value ? (split === "vertical" ? "col-resize" : "row-resize") : "" active.value ? (props.split === "vertical" ? "col-resize" : "row-resize") : ""
) )
// watch( // watch(
@@ -89,7 +91,7 @@ function onMouseMove(e: any) {
if (active.value) { if (active.value) {
let offset = 0 let offset = 0
let target = e.currentTarget let target = e.currentTarget
if (split === "vertical") { if (props.split === "vertical") {
while (target) { while (target) {
offset += target.offsetLeft offset += target.offsetLeft
target = target.offsetParent target = target.offsetParent
@@ -100,14 +102,14 @@ function onMouseMove(e: any) {
target = target.offsetParent target = target.offsetParent
} }
} }
const currentPage = split === "vertical" ? e.pageX : e.pageY const currentPage = props.split === "vertical" ? e.pageX : e.pageY
const targetOffset = const targetOffset =
split === "vertical" props.split === "vertical"
? e.currentTarget.offsetWidth ? e.currentTarget.offsetWidth
: e.currentTarget.offsetHeight : e.currentTarget.offsetHeight
const newPercent = const newPercent =
Math.floor(((currentPage - offset) / targetOffset) * 10000) / 100 Math.floor(((currentPage - offset) / targetOffset) * 10000) / 100
if (newPercent > minPercent && newPercent < 100 - minPercent) { if (newPercent > props.minPercent && newPercent < 100 - props.minPercent) {
percent.value = newPercent percent.value = newPercent
} }
emit("resize", newPercent) emit("resize", newPercent)

View File

@@ -5,12 +5,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const { className, split } = defineProps<{ import { computed } from "vue"
interface Props {
split: "horizontal" | "vertical" split: "horizontal" | "vertical"
className?: string className?: string
}>() }
const props = withDefaults(defineProps<Props>(), {
split: "horizontal",
className: "",
})
const classes = $computed(() => [split, className].join(" ")) const classes = computed(() => [props.split, props.className].join(" "))
</script> </script>
<style scoped> <style scoped>

View File

@@ -5,13 +5,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue" import { computed } from "vue"
const { className, split } = defineProps<{ interface Props {
split: "horizontal" | "vertical" split: "horizontal" | "vertical"
className?: string className?: string
}>() }
const props = withDefaults(defineProps<Props>(), {
split: "horizontal",
className: "",
})
const classes = computed(() => const classes = computed(() =>
["splitter-pane-resizer", split, className].join(" ") ["splitter-pane-resizer", props.split, props.className].join(" ")
) )
</script> </script>