add result panel.
This commit is contained in:
7
components.d.ts
vendored
7
components.d.ts
vendored
@@ -24,6 +24,7 @@ declare module '@vue/runtime-core' {
|
|||||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
|
ElLink: typeof import('element-plus/es')['ElLink']
|
||||||
ElMain: typeof import('element-plus/es')['ElMain']
|
ElMain: typeof import('element-plus/es')['ElMain']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||||
@@ -38,10 +39,14 @@ declare module '@vue/runtime-core' {
|
|||||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
|
IEpBell: typeof import('~icons/ep/bell')['default']
|
||||||
|
'IEpCaret-': typeof import('~icons/ep/caret-')['default']
|
||||||
|
IEpCaretRight: typeof import('~icons/ep/caret-right')['default']
|
||||||
|
IEpLoading: typeof import('~icons/ep/loading')['default']
|
||||||
IEpSelect: typeof import('~icons/ep/select')['default']
|
IEpSelect: typeof import('~icons/ep/select')['default']
|
||||||
IEpSemiSelect: typeof import('~icons/ep/semi-select')['default']
|
IEpSemiSelect: typeof import('~icons/ep/semi-select')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
UseNetwork: typeof import('@vueuse/components')['UseNetwork']
|
UseNetwork: typeof import("@vueuse/components")["UseNetwork"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -15,6 +15,7 @@
|
|||||||
"axios": "^1.2.2",
|
"axios": "^1.2.2",
|
||||||
"element-plus": "^2.2.28",
|
"element-plus": "^2.2.28",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
"party-js": "^2.2.0",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.0.28",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-router": "^4.1.6"
|
"vue-router": "^4.1.6"
|
||||||
@@ -1618,6 +1619,11 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/party-js": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/party-js/-/party-js-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-50hGuALCpvDTrQLPQ1fgUgxKIWAH28ShVkmeK/3zhO0YJyCqkhrZhQEkWPxDYLvbFJ7YAXyROmFEu35gKpZLtQ=="
|
||||||
|
},
|
||||||
"node_modules/path-exists": {
|
"node_modules/path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
@@ -3324,6 +3330,11 @@
|
|||||||
"p-limit": "^3.0.2"
|
"p-limit": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"party-js": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/party-js/-/party-js-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-50hGuALCpvDTrQLPQ1fgUgxKIWAH28ShVkmeK/3zhO0YJyCqkhrZhQEkWPxDYLvbFJ7YAXyROmFEu35gKpZLtQ=="
|
||||||
|
},
|
||||||
"path-exists": {
|
"path-exists": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"axios": "^1.2.2",
|
"axios": "^1.2.2",
|
||||||
"element-plus": "^2.2.28",
|
"element-plus": "^2.2.28",
|
||||||
"normalize.css": "^8.0.1",
|
"normalize.css": "^8.0.1",
|
||||||
|
"party-js": "^2.2.0",
|
||||||
"pinia": "^2.0.28",
|
"pinia": "^2.0.28",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-router": "^4.1.6"
|
"vue-router": "^4.1.6"
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import zhCn from "element-plus/dist/locale/zh-cn.mjs"
|
import zhCn from "element-plus/dist/locale/zh-cn.mjs"
|
||||||
const locale = zhCn
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-config-provider :locale="locale">
|
<el-config-provider :locale="zhCn" :button="{ autoInsertSpace: true }">
|
||||||
<router-view></router-view>
|
<router-view></router-view>
|
||||||
</el-config-provider>
|
</el-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
48
src/main.ts
48
src/main.ts
@@ -5,58 +5,12 @@ import "normalize.css"
|
|||||||
import loader from "@monaco-editor/loader"
|
import loader from "@monaco-editor/loader"
|
||||||
|
|
||||||
import App from "./App.vue"
|
import App from "./App.vue"
|
||||||
import Home from "./oj/index.vue"
|
|
||||||
import Problems from "./oj/problem/list.vue"
|
|
||||||
|
|
||||||
import storage from "./utils/storage"
|
import storage from "./utils/storage"
|
||||||
|
import routes from "./routes"
|
||||||
import { STORAGE_KEY } from "./utils/constants"
|
import { STORAGE_KEY } from "./utils/constants"
|
||||||
import { useLoginStore } from "./shared/stores/login"
|
import { useLoginStore } from "./shared/stores/login"
|
||||||
|
|
||||||
const routes = [
|
|
||||||
{
|
|
||||||
path: "/",
|
|
||||||
component: Home,
|
|
||||||
children: [
|
|
||||||
{ path: "", component: Problems },
|
|
||||||
{
|
|
||||||
path: "problem/:problemID",
|
|
||||||
component: () => import("./oj/problem/detail.vue"),
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "status",
|
|
||||||
component: () => import("./oj/status/list.vue"),
|
|
||||||
meta: { requiresAuth: true },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "status/:statusID",
|
|
||||||
component: () => import("./oj/status/detail.vue"),
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "contest",
|
|
||||||
component: () => import("./oj/contest/list.vue"),
|
|
||||||
meta: { requiresAuth: true },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "contest/:contestID",
|
|
||||||
component: () => import("./oj/contest/detail.vue"),
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "contest/:contestID/problem/:problemID",
|
|
||||||
component: () => import("./oj/problem/detail.vue"),
|
|
||||||
props: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: "rank",
|
|
||||||
component: () => import("./oj/rank/list.vue"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ path: "/admin", component: () => import("./admin/index.vue") },
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes,
|
routes,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getACRate } from "./../utils/functions"
|
import { getACRate } from "./../utils/functions"
|
||||||
import { DIFFICULTY } from "./../utils/constants"
|
import { DIFFICULTY } from "./../utils/constants"
|
||||||
import { Problem, SubmitCodePayload } from "./../utils/types"
|
import { Problem, SubmitCodePayload, Submission } from "./../utils/types"
|
||||||
import http from "./../utils/http"
|
import http from "./../utils/http"
|
||||||
import { useAxios } from "@vueuse/integrations/useAxios"
|
import { useAxios } from "@vueuse/integrations/useAxios"
|
||||||
|
|
||||||
@@ -60,11 +60,17 @@ export function getProblem(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getSubmission(id: string) {
|
export function getSubmission(id: string) {
|
||||||
return http.get("submission", {
|
return http.get<Submission>("submission", {
|
||||||
params: { id },
|
params: { id },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function submissionExists(problemID: number) {
|
||||||
|
return http.get("submission_exists", {
|
||||||
|
params: { problem_id: problemID },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function submitCode(data: SubmitCodePayload) {
|
export function submitCode(data: SubmitCodePayload) {
|
||||||
return http.post("submission", data)
|
return http.post("submission", data)
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/oj/components/submission-result-tag.vue
Normal file
16
src/oj/components/submission-result-tag.vue
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { JUDGE_STATUS } from "../../utils/constants"
|
||||||
|
import { SUBMISSION_RESULT } from "../../utils/types"
|
||||||
|
|
||||||
|
const { result } = defineProps<{
|
||||||
|
result: SUBMISSION_RESULT
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-tag :type="JUDGE_STATUS[result]['type']" disable-transitions>
|
||||||
|
{{ JUDGE_STATUS[result]["name"] }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>contest detail</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
|
|||||||
277
src/oj/problem/components/editor-exec.vue
Normal file
277
src/oj/problem/components/editor-exec.vue
Normal 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>
|
||||||
@@ -1,39 +1,28 @@
|
|||||||
<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, computed } from "vue"
|
import { ref, onBeforeUnmount, onMounted, watch, reactive } from "vue"
|
||||||
import {
|
import {
|
||||||
LANGUAGE_LABEL,
|
LANGUAGE_LABEL,
|
||||||
LANGUAGE_VALUE,
|
LANGUAGE_VALUE,
|
||||||
SOURCES,
|
SOURCES,
|
||||||
} from "../../../utils/constants"
|
} from "../../../utils/constants"
|
||||||
import { isMobile } from "../../../utils/breakpoints"
|
import { isMobile } from "../../../utils/breakpoints"
|
||||||
import { submitCode } from "../../api"
|
import { Problem } from "../../../utils/types"
|
||||||
import { LANGUAGE, Problem, SubmitCodePayload } from "../../../utils/types"
|
import EditorExec from "./editor-exec.vue"
|
||||||
|
|
||||||
const { problem, contestID = "" } = defineProps<{
|
const { problem } = defineProps<{ problem: Problem }>()
|
||||||
contestID?: string
|
|
||||||
problemID?: string
|
|
||||||
problem: Problem
|
|
||||||
}>()
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
values: ref({ ...SOURCES }),
|
code: SOURCES[problem.languages[0] || "C"],
|
||||||
language: problem.languages[0] || "C",
|
language: problem.languages[0] || "C",
|
||||||
isMobile,
|
isMobile,
|
||||||
submissionId: "",
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const monacoEditorRef = ref()
|
const monacoEditorRef = ref()
|
||||||
|
|
||||||
let monaco: Monaco
|
let monaco: Monaco
|
||||||
|
|
||||||
function reset() {
|
onMounted(() => {
|
||||||
state.values[state.language] =
|
init()
|
||||||
problem.template[state.language] || SOURCES[state.language]
|
})
|
||||||
if (monaco && monaco.editor) {
|
|
||||||
monaco.editor.getModels()[0].setValue(state.values[state.language])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(init)
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
monaco.editor.getModels().forEach((model) => model.dispose())
|
monaco.editor.getModels().forEach((model) => model.dispose())
|
||||||
@@ -52,13 +41,18 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
async function init() {
|
function reset() {
|
||||||
state.values[state.language] =
|
state.code = problem.template[state.language] || SOURCES[state.language]
|
||||||
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 = await loader.init()
|
||||||
monaco.editor.create(monacoEditorRef.value, {
|
monaco.editor.create(monacoEditorRef.value, {
|
||||||
value: state.values[state.language], // 编辑器初始显示文字
|
value: state.code, // 编辑器初始显示文字
|
||||||
language: LANGUAGE_VALUE[state.language],
|
language: LANGUAGE_VALUE[state.language],
|
||||||
theme: "vs", // 官方自带三种主题vs, hc-black, or vs-dark
|
theme: "vs", // 官方自带三种主题vs, hc-black, or vs-dark
|
||||||
minimap: {
|
minimap: {
|
||||||
@@ -67,39 +61,17 @@ async function init() {
|
|||||||
lineNumbersMinChars: 3,
|
lineNumbersMinChars: 3,
|
||||||
automaticLayout: true, // 自适应布局
|
automaticLayout: true, // 自适应布局
|
||||||
tabSize: 4,
|
tabSize: 4,
|
||||||
fontSize: state.isMobile ? 16 : 24, // 字体大小
|
fontSize: state.isMobile ? 20 : 24, // 字体大小
|
||||||
scrollBeyondLastLine: false, // 取消代码后面一大段空白
|
scrollBeyondLastLine: false, // 取消代码后面一大段空白
|
||||||
})
|
})
|
||||||
monaco.editor.getModels()[0].onDidChangeContent(() => {
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form :inline="true">
|
<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="state.language" class="language">
|
||||||
<el-option
|
<el-option
|
||||||
@@ -115,22 +87,11 @@ async function submit() {
|
|||||||
<el-button @click="reset">重置</el-button>
|
<el-button @click="reset">重置</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<div
|
<section
|
||||||
ref="monacoEditorRef"
|
ref="monacoEditorRef"
|
||||||
:class="isMobile ? 'editorMobile' : 'editor'"
|
:class="isMobile ? 'editorMobile' : 'editor'"
|
||||||
></div>
|
></section>
|
||||||
<el-tabs type="border-card">
|
<EditorExec :state="state" :problem="problem" />
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -139,15 +100,11 @@ async function submit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
height: 70%;
|
/* 141px+400 */
|
||||||
|
height: calc(100vh - 541px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.editorMobile {
|
.editorMobile {
|
||||||
height: 500px;
|
height: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
|
||||||
margin-top: 16px;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const { data: problem, isFinished } = getProblem(problemID)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-row v-if="isFinished && problem">
|
<el-row v-if="isFinished && problem" :gutter="20">
|
||||||
<el-col :span="isDesktop ? 12 : 24">
|
<el-col :span="isDesktop ? 12 : 24">
|
||||||
<el-tabs type="border-card">
|
<el-tabs type="border-card">
|
||||||
<el-tab-pane label="题目描述">
|
<el-tab-pane label="题目描述">
|
||||||
@@ -29,17 +29,13 @@ const { data: problem, isFinished } = getProblem(problemID)
|
|||||||
<el-tab-pane label="题目信息" lazy>
|
<el-tab-pane label="题目信息" lazy>
|
||||||
<ProblemInfo :problem="problem" />
|
<ProblemInfo :problem="problem" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="提交情况">3</el-tab-pane>
|
<el-tab-pane label="提交列表">3</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col v-if="isDesktop" :span="12" class="editorWrapper">
|
<el-col v-if="isDesktop" :span="12">
|
||||||
<Editor :problem="problem" />
|
<Editor :problem="problem" />
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped></style>
|
||||||
.editorWrapper {
|
|
||||||
height: calc(100vh - 171px);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ onMounted(listProblems)
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<el-form :inline="true">
|
<el-form inline>
|
||||||
<el-form-item label="难度">
|
<el-form-item label="难度">
|
||||||
<el-select v-model="query.difficulty">
|
<el-select v-model="query.difficulty">
|
||||||
<el-option
|
<el-option
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { useToggle } from "@vueuse/core"
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { ref } from "vue"
|
|
||||||
|
|
||||||
export const useSignupStore = defineStore("signup", () => {
|
export const useSignupStore = defineStore("signup", () => {
|
||||||
const visible = ref(false)
|
const [visible] = useToggle()
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
|||||||
49
src/routes.ts
Normal file
49
src/routes.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import Home from "./oj/index.vue"
|
||||||
|
import Problems from "./oj/problem/list.vue"
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
component: Home,
|
||||||
|
children: [
|
||||||
|
{ path: "", component: Problems },
|
||||||
|
{
|
||||||
|
path: "problem/:problemID",
|
||||||
|
component: () => import("./oj/problem/detail.vue"),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "status",
|
||||||
|
component: () => import("./oj/status/list.vue"),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "status/:statusID",
|
||||||
|
component: () => import("./oj/status/detail.vue"),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "contest",
|
||||||
|
component: () => import("./oj/contest/list.vue"),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "contest/:contestID",
|
||||||
|
component: () => import("./oj/contest/detail.vue"),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "contest/:contestID/problem/:problemID",
|
||||||
|
component: () => import("./oj/problem/detail.vue"),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "rank",
|
||||||
|
component: () => import("./oj/rank/list.vue"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ path: "/admin", component: () => import("./admin/index.vue") },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default routes
|
||||||
140
src/shared/split-panel/index.vue
Normal file
140
src/shared/split-panel/index.vue
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:style="{ cursor, userSelect }"
|
||||||
|
class="vue-splitter-container clearfix"
|
||||||
|
@mouseup="onMouseUp"
|
||||||
|
@mousemove="onMouseMove"
|
||||||
|
>
|
||||||
|
<Pane
|
||||||
|
class="splitter-pane splitter-paneL"
|
||||||
|
:split="split"
|
||||||
|
:style="{ [type]: percent + '%' }"
|
||||||
|
>
|
||||||
|
<slot name="panel"></slot>
|
||||||
|
</Pane>
|
||||||
|
|
||||||
|
<Resizer
|
||||||
|
:className="className"
|
||||||
|
:style="{ [resizeType]: percent + '%' }"
|
||||||
|
:split="split"
|
||||||
|
@mousedown.native="onMouseDown"
|
||||||
|
@click.native="onClick"
|
||||||
|
></Resizer>
|
||||||
|
|
||||||
|
<Pane
|
||||||
|
class="splitter-pane splitter-paneR"
|
||||||
|
:split="split"
|
||||||
|
:style="{ [type]: 100 - percent + '%' }"
|
||||||
|
>
|
||||||
|
<slot name="paner"></slot>
|
||||||
|
</Pane>
|
||||||
|
<div class="vue-splitter-container-mask" v-if="active"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Resizer from "./resizer.vue"
|
||||||
|
import Pane from "./pane.vue"
|
||||||
|
import { computed, ref } from "vue"
|
||||||
|
|
||||||
|
const {
|
||||||
|
minPercent = 10,
|
||||||
|
defaultPercent = 50,
|
||||||
|
split,
|
||||||
|
className,
|
||||||
|
} = defineProps<{
|
||||||
|
minPercent?: number
|
||||||
|
defaultPercent?: number
|
||||||
|
split: "vertical" | "horizontal"
|
||||||
|
className?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits(["resize"])
|
||||||
|
|
||||||
|
const active = ref(false)
|
||||||
|
const hasMoved = ref(false)
|
||||||
|
const percent = ref(defaultPercent)
|
||||||
|
const type = ref(split === "vertical" ? "width" : "height")
|
||||||
|
const resizeType = ref(split === "vertical" ? "left" : "top")
|
||||||
|
|
||||||
|
const userSelect = computed(() => (active.value ? "none" : "auto"))
|
||||||
|
const cursor = computed(() =>
|
||||||
|
active.value ? (split === "vertical" ? "col-resize" : "row-resize") : ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// watch(
|
||||||
|
// () => defaultPercent,
|
||||||
|
// (newValue) => {
|
||||||
|
// percent.value = newValue
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
if (!hasMoved.value) {
|
||||||
|
percent.value = 50
|
||||||
|
emit("resize", percent.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function onMouseDown() {
|
||||||
|
active.value = true
|
||||||
|
hasMoved.value = false
|
||||||
|
}
|
||||||
|
function onMouseUp() {
|
||||||
|
active.value = false
|
||||||
|
}
|
||||||
|
function onMouseMove(e: any) {
|
||||||
|
if (e.buttons === 0) {
|
||||||
|
active.value = false
|
||||||
|
}
|
||||||
|
if (active.value) {
|
||||||
|
let offset = 0
|
||||||
|
let target = e.currentTarget
|
||||||
|
if (split === "vertical") {
|
||||||
|
while (target) {
|
||||||
|
offset += target.offsetLeft
|
||||||
|
target = target.offsetParent
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (target) {
|
||||||
|
offset += target.offsetTop
|
||||||
|
target = target.offsetParent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const currentPage = split === "vertical" ? e.pageX : e.pageY
|
||||||
|
const targetOffset =
|
||||||
|
split === "vertical"
|
||||||
|
? e.currentTarget.offsetWidth
|
||||||
|
: e.currentTarget.offsetHeight
|
||||||
|
const newPercent =
|
||||||
|
Math.floor(((currentPage - offset) / targetOffset) * 10000) / 100
|
||||||
|
if (newPercent > minPercent && newPercent < 100 - minPercent) {
|
||||||
|
percent.value = newPercent
|
||||||
|
}
|
||||||
|
emit("resize", newPercent)
|
||||||
|
hasMoved.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.clearfix:after {
|
||||||
|
visibility: hidden;
|
||||||
|
display: block;
|
||||||
|
font-size: 0;
|
||||||
|
content: " ";
|
||||||
|
clear: both;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
.vue-splitter-container {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.vue-splitter-container-mask {
|
||||||
|
z-index: 9999;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
40
src/shared/split-panel/pane.vue
Normal file
40
src/shared/split-panel/pane.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="classes">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { className, split } = defineProps<{
|
||||||
|
split: "horizontal" | "vertical"
|
||||||
|
className?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const classes = $computed(() => [split, className].join(" "))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.splitter-pane.vertical.splitter-paneL {
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
height: 100%;
|
||||||
|
padding-right: 3px;
|
||||||
|
}
|
||||||
|
.splitter-pane.vertical.splitter-paneR {
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
height: 100%;
|
||||||
|
padding-left: 3px;
|
||||||
|
}
|
||||||
|
.splitter-pane.horizontal.splitter-paneL {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.splitter-pane.horizontal.splitter-paneR {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0px;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
43
src/shared/split-panel/resizer.vue
Normal file
43
src/shared/split-panel/resizer.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="classes"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from "vue"
|
||||||
|
|
||||||
|
const { className, split } = defineProps<{
|
||||||
|
split: "horizontal" | "vertical"
|
||||||
|
className?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const classes = computed(() =>
|
||||||
|
["splitter-pane-resizer", split, className].join(" ")
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.splitter-pane-resizer {
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #000;
|
||||||
|
position: absolute;
|
||||||
|
opacity: 0.2;
|
||||||
|
z-index: 1;
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
.splitter-pane-resizer.horizontal {
|
||||||
|
height: 11px;
|
||||||
|
margin: -5px 0;
|
||||||
|
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||||
|
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||||
|
cursor: row-resize;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.splitter-pane-resizer.vertical {
|
||||||
|
width: 11px;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: -5px;
|
||||||
|
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||||
|
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||||
|
cursor: col-resize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
|
import { useToggle } from "@vueuse/core"
|
||||||
import { defineStore } from "pinia"
|
import { defineStore } from "pinia"
|
||||||
import { ref } from "vue"
|
|
||||||
|
|
||||||
export const useLoginStore = defineStore("login", () => {
|
export const useLoginStore = defineStore("login", () => {
|
||||||
const visible = ref(false)
|
const [visible] = useToggle()
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
visible.value = true
|
visible.value = true
|
||||||
|
|||||||
@@ -1,72 +1,65 @@
|
|||||||
|
export enum SubmissionStatus {
|
||||||
|
compile_error = -2,
|
||||||
|
wrong_answer = -1,
|
||||||
|
accepted = 0,
|
||||||
|
time_limit_exceeded = 1 | 2,
|
||||||
|
memory_limit_exceeded = 3,
|
||||||
|
runtime_error = 4,
|
||||||
|
system_error = 5,
|
||||||
|
pending = 6,
|
||||||
|
judging = 7,
|
||||||
|
partial_accepted = 8,
|
||||||
|
submitting = 9,
|
||||||
|
}
|
||||||
|
|
||||||
export const JUDGE_STATUS = {
|
export const JUDGE_STATUS = {
|
||||||
"-2": {
|
"-2": {
|
||||||
name: "Compile Error",
|
name: "编译失败",
|
||||||
short: "CE",
|
type: "error",
|
||||||
color: "yellow",
|
|
||||||
type: "warning",
|
|
||||||
},
|
},
|
||||||
"-1": {
|
"-1": {
|
||||||
name: "Wrong Answer",
|
name: "答案错误",
|
||||||
short: "WA",
|
|
||||||
color: "red",
|
|
||||||
type: "error",
|
type: "error",
|
||||||
},
|
},
|
||||||
"0": {
|
"0": {
|
||||||
name: "Accepted",
|
name: "答案正确",
|
||||||
short: "AC",
|
|
||||||
color: "green",
|
|
||||||
type: "success",
|
type: "success",
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
name: "Time Limit Exceeded",
|
name: "运行超时",
|
||||||
short: "TLE",
|
|
||||||
color: "red",
|
|
||||||
type: "error",
|
type: "error",
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
name: "Time Limit Exceeded",
|
name: "运行超时",
|
||||||
short: "TLE",
|
|
||||||
color: "red",
|
|
||||||
type: "error",
|
type: "error",
|
||||||
},
|
},
|
||||||
"3": {
|
"3": {
|
||||||
name: "Memory Limit Exceeded",
|
name: "内存超限",
|
||||||
short: "MLE",
|
|
||||||
color: "red",
|
|
||||||
type: "error",
|
type: "error",
|
||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
name: "Runtime Error",
|
name: "运行时错误",
|
||||||
short: "RE",
|
|
||||||
color: "red",
|
|
||||||
type: "error",
|
type: "error",
|
||||||
},
|
},
|
||||||
"5": {
|
"5": {
|
||||||
name: "System Error",
|
name: "系统错误",
|
||||||
short: "SE",
|
|
||||||
color: "red",
|
|
||||||
type: "error",
|
type: "error",
|
||||||
},
|
},
|
||||||
"6": {
|
"6": {
|
||||||
name: "Pending",
|
name: "等待评分",
|
||||||
color: "yellow",
|
|
||||||
type: "warning",
|
type: "warning",
|
||||||
},
|
},
|
||||||
"7": {
|
"7": {
|
||||||
name: "Judging",
|
name: "正在评分",
|
||||||
color: "blue",
|
|
||||||
type: "info",
|
type: "info",
|
||||||
},
|
},
|
||||||
"8": {
|
"8": {
|
||||||
name: "Partial Accepted",
|
name: "部分正确",
|
||||||
short: "PAC",
|
type: "warning",
|
||||||
color: "blue",
|
|
||||||
type: "info",
|
|
||||||
},
|
},
|
||||||
"9": {
|
"9": {
|
||||||
name: "Submitting",
|
name: "正在提交",
|
||||||
color: "yellow",
|
type: "info",
|
||||||
type: "warning",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,3 +38,15 @@ export function parseTime(utc: Date, format = "YYYY年M月D日") {
|
|||||||
const time = useDateFormat(utc, format, { locales: "zh-CN" })
|
const time = useDateFormat(utc, format, { locales: "zh-CN" })
|
||||||
return time.value
|
return time.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function submissionMemoryFormat(memory: string) {
|
||||||
|
if (memory === undefined) return "--"
|
||||||
|
// 1048576 = 1024 * 1024
|
||||||
|
let t = parseInt(memory) / 1048576
|
||||||
|
return String(t.toFixed(0)) + "MB"
|
||||||
|
}
|
||||||
|
|
||||||
|
export function submissionTimeFormat(time: number) {
|
||||||
|
if (time === undefined) return "--"
|
||||||
|
return time + "ms"
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ const http = axios.create({
|
|||||||
xsrfCookieName: "csrftoken",
|
xsrfCookieName: "csrftoken",
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO
|
|
||||||
http.interceptors.response.use(
|
http.interceptors.response.use(
|
||||||
(res) => {
|
(res) => {
|
||||||
if (res.data.error) {
|
if (res.data.error) {
|
||||||
// 若后端返回为登录,则为session失效,应退出当前登录用户
|
// // TODO: 若后端返回为登录,则为session失效,应退出当前登录用户
|
||||||
if (res.data.data.startsWith("Please login")) {
|
if (res.data.data.startsWith("Please login")) {
|
||||||
}
|
}
|
||||||
return Promise.reject(res.data)
|
return Promise.reject(res.data)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export type LANGUAGE =
|
|||||||
| "JavaScript"
|
| "JavaScript"
|
||||||
| "Golang"
|
| "Golang"
|
||||||
|
|
||||||
|
export type SUBMISSION_RESULT = -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
|
||||||
|
|
||||||
export interface Problem {
|
export interface Problem {
|
||||||
_id: string
|
_id: string
|
||||||
id: number
|
id: number
|
||||||
@@ -56,3 +58,40 @@ export interface SubmitCodePayload {
|
|||||||
code: string
|
code: string
|
||||||
contest_id?: number
|
contest_id?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Info {
|
||||||
|
err: string | null
|
||||||
|
data: {
|
||||||
|
error: number
|
||||||
|
memory: number
|
||||||
|
output: null
|
||||||
|
result: SUBMISSION_RESULT
|
||||||
|
signal: number
|
||||||
|
cpu_time: number
|
||||||
|
exit_code: number
|
||||||
|
real_time: number
|
||||||
|
test_case: string
|
||||||
|
output_md5: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Submission {
|
||||||
|
id: string
|
||||||
|
create_time: Date
|
||||||
|
user_id: number
|
||||||
|
username: string
|
||||||
|
code: string
|
||||||
|
result: SUBMISSION_RESULT
|
||||||
|
info: Info
|
||||||
|
language: string
|
||||||
|
shared: boolean
|
||||||
|
statistic_info: {
|
||||||
|
score: number
|
||||||
|
err_info: string
|
||||||
|
}
|
||||||
|
ip: string
|
||||||
|
// TODO: 这里不知道是什么
|
||||||
|
contest: null
|
||||||
|
problem: number
|
||||||
|
can_unshare: boolean
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user