Files
ojnext/src/oj/submission/list.vue
2025-04-14 14:45:31 +08:00

366 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { NButton, NH2, NText } from "naive-ui"
import { adminRejudge, getSubmissions, getTodaySubmissionCount } from "oj/api"
import {
filterEmptyValue,
parseTime,
submissionMemoryFormat,
submissionTimeFormat,
} from "utils/functions"
import { Submission } from "utils/types"
import Pagination from "~/shared/components/Pagination.vue"
import SubmissionResultTag from "~/shared/components/SubmissionResultTag.vue"
import { isDesktop } from "~/shared/composables/breakpoints"
import { useUserStore } from "~/shared/store/user"
import { LANGUAGE_SHOW_VALUE } from "~/utils/constants"
import { renderTableTitle } from "~/utils/renders"
import ButtonWithSearch from "./components/ButtonWithSearch.vue"
import StatisticsPanel from "./components/StatisticsPanel.vue"
import SubmissionLink from "./components/SubmissionLink.vue"
import SubmissionDetail from "./detail.vue"
interface Query {
username: string
result: string
limit: number
page: number
myself: boolean
problem: string
}
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const message = useMessage()
const submissions = ref([])
const total = ref(0)
const todayCount = ref(0)
const query = reactive<Query>({
result: <string>route.query.result ?? "",
page: parseInt(<string>route.query.page) || 1,
limit: parseInt(<string>route.query.limit) || 10,
username: <string>route.query.username ?? "",
myself: route.query.myself === "1",
problem: <string>route.query.problem ?? "",
})
const submissionID = ref("")
const [statisticPanel, toggleStatisticPanel] = useToggle(false)
const [codePanel, toggleCodePanel] = useToggle(false)
const options: SelectOption[] = [
{ label: "全部", value: "" },
{ label: "答案正确", value: "0" },
{ label: "答案错误", value: "-1" },
{ label: "编译失败", value: "-2" },
{ label: "运行时错误", value: "4" },
]
async function listSubmissions() {
query.result = <string>route.query.result ?? ""
query.page = parseInt(<string>route.query.page) || 1
query.limit = parseInt(<string>route.query.limit) || 10
query.username = <string>route.query.username ?? ""
query.myself = route.query.myself === "1"
query.problem = <string>route.query.problem ?? ""
if (query.page < 1) query.page = 1
const offset = query.limit * (query.page - 1)
const res = await getSubmissions({
...query,
myself: query.myself ? "1" : "0",
offset,
problem_id: <string>route.query.problem ?? "",
contest_id: <string>route.params.contestID ?? "",
})
submissions.value = res.data.results
total.value = res.data.total
}
async function getTodayCount() {
const res = await getTodaySubmissionCount()
todayCount.value = res.data
}
onMounted(() => {
listSubmissions()
if (route.name === "submissions") {
getTodayCount()
}
})
function routerPush() {
const newQuery = {
...query,
myself: query.myself ? "1" : "0",
}
router.push({
path: route.path,
query: filterEmptyValue(newQuery),
})
}
function search(username: string, problem: string) {
query.username = username
query.problem = problem
}
function clear() {
query.username = ""
query.myself = false
query.result = ""
query.problem = ""
}
async function rejudge(submissionID: string) {
await adminRejudge(submissionID)
message.success("重新判分成功")
listSubmissions()
}
function problemClicked(row: Submission) {
if (route.name === "contest submissions") {
const path = router.resolve({
name: "contest problem",
params: {
problemID: row.problem,
},
})
window.open(path.href, "_blank")
} else {
window.open("/problem/" + row.problem, "_blank")
}
}
function showCodePanel(id: string) {
toggleCodePanel(true)
submissionID.value = id
}
watch(() => query.page, routerPush)
watch(
() => [query.limit, query.myself, query.result],
() => {
query.page = 1
routerPush()
},
)
watchDebounced(
() => [query.username, query.problem],
() => {
query.page = 1
routerPush()
},
{ debounce: 500, maxWait: 1000 },
)
watch(
() =>
(route.name === "submissions" || route.name === "contest submissions") &&
route.query,
(newVal) => {
if (newVal) listSubmissions()
},
)
const columns = computed(() => {
const res: DataTableColumn<Submission>[] = [
{
title: renderTableTitle("提交时间", "noto:seven-oclock"),
key: "create_time",
width: 200,
render: (row) => parseTime(row.create_time, "YYYY-MM-DD HH:mm:ss"),
},
{
title: renderTableTitle("提交编号", "fluent-emoji-flat:input-numbers"),
key: "id",
minWidth: 160,
render: (row) =>
h(SubmissionLink, {
submission: row,
onShowCode: () => showCodePanel(row.id),
}),
},
{
title: renderTableTitle("状态", "streamline-emojis:panda-face"),
key: "status",
width: 160,
render: (row) => h(SubmissionResultTag, { result: row.result }),
},
{
title: renderTableTitle("题目", "streamline-emojis:blossom"),
key: "problem",
width: 160,
render: (row) =>
h(
ButtonWithSearch,
{
onClick: () => problemClicked(row),
onSearch: () => (query.problem = row.problem),
},
() => row.problem,
),
},
{
title: renderTableTitle("执行耗时", "streamline-emojis:snail"),
key: "time",
width: 120,
align: "center",
render: (row) => submissionTimeFormat(row.statistic_info.time_cost),
},
{
title: renderTableTitle("占用内存", "streamline-emojis:bell"),
key: "memory",
width: 120,
align: "center",
render: (row) => submissionMemoryFormat(row.statistic_info.memory_cost),
},
{
title: renderTableTitle(
"语言",
"streamline-emojis:globe-showing-europe-africa",
),
key: "language",
width: 120,
render: (row) => LANGUAGE_SHOW_VALUE[row.language],
},
{
title: renderTableTitle(
"用户",
"streamline-emojis:smiling-face-with-sunglasses",
),
key: "username",
minWidth: 160,
render: (row) =>
h(
ButtonWithSearch,
{
onClick: () => window.open("/user?name=" + row.username, "_blank"),
onSearch: () => (query.username = row.username),
},
() => row.username,
),
},
]
if (!route.params.contestID && userStore.isSuperAdmin) {
res.push({
title: renderTableTitle("选项", "streamline-emojis:wrench"),
key: "rejudge",
render: (row) =>
h(
NButton,
{
quaternary: true,
size: "small",
type: "primary",
onClick: () => rejudge(row.id),
},
() => "重新判题",
),
})
}
return res
})
</script>
<template>
<n-flex vertical size="large">
<n-space>
<n-form :show-feedback="false" inline label-placement="left">
<n-form-item label="提交状态">
<n-select
class="select"
v-model:value="query.result"
:options="options"
/>
</n-form-item>
<n-form-item v-if="userStore.isAuthed" label="只看自己">
<n-switch v-model:value="query.myself" />
</n-form-item>
</n-form>
<n-form :show-feedback="false" inline label-placement="left">
<n-form-item>
<n-input
class="input"
clearable
v-model:value="query.username"
placeholder="用户"
/>
</n-form-item>
<n-form-item>
<n-input
class="input"
clearable
v-model:value="query.problem"
placeholder="题号"
/>
</n-form-item>
</n-form>
<n-form :show-feedback="false" inline label-placement="left">
<n-form-item>
<n-button @click="search(query.username, query.problem)">
搜索
</n-button>
</n-form-item>
<n-form-item
v-if="userStore.isSuperAdmin && route.name === 'submissions'"
>
<n-button @click="toggleStatisticPanel(true)">数据统计</n-button>
</n-form-item>
<n-form-item>
<n-button @click="clear" quaternary>重置</n-button>
</n-form-item>
<n-form-item v-if="todayCount > 0">
<component :is="isDesktop ? NH2 : NText" class="todayCount">
<n-gradient-text>今日提交数{{ todayCount }}</n-gradient-text>
</component>
</n-form-item>
</n-form>
</n-space>
<n-data-table striped :columns="columns" :data="submissions" />
</n-flex>
<Pagination
:total="total"
v-model:limit="query.limit"
v-model:page="query.page"
/>
<n-modal
v-if="userStore.isSuperAdmin"
v-model:show="statisticPanel"
preset="card"
:style="{ maxWidth: isDesktop && '70vw', maxHeight: '80vh' }"
:content-style="{ overflow: 'auto' }"
title="提交记录的统计"
>
<StatisticsPanel :problem="query.problem" :username="query.username" />
</n-modal>
<n-modal
v-model:show="codePanel"
preset="card"
:style="{ maxWidth: isDesktop && '70vw', maxHeight: '80vh' }"
:content-style="{ overflow: 'auto' }"
title="代码详情"
>
<SubmissionDetail :submissionID="submissionID" hideList />
</n-modal>
</template>
<style scoped>
.select {
width: 120px;
}
.input {
width: 200px;
}
.code {
font-size: 20px;
overflow: auto;
}
.todayCount {
margin: 0;
}
</style>