更新首页列表
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "oj-next",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "oj-next",
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@codemirror/lang-cpp": "^6.0.2",
|
||||
"@codemirror/lang-python": "^6.1.3",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "oj-next",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
||||
@@ -19,7 +19,7 @@ export async function getProblemList(
|
||||
limit = 10,
|
||||
keyword: string,
|
||||
contestID?: string,
|
||||
ruleType?: "ACM" | "OI"
|
||||
ruleType?: "ACM" | "OI",
|
||||
) {
|
||||
const endpoint = !!contestID ? "admin/contest/problem" : "admin/problem"
|
||||
const res = await http.get(endpoint, {
|
||||
@@ -143,7 +143,7 @@ export function getContest(id: string) {
|
||||
export function addProblemForContest(
|
||||
contestID: string,
|
||||
problemID: number,
|
||||
displayID: string
|
||||
displayID: string,
|
||||
) {
|
||||
return http.post("admin/contest/add_problem_from_public", {
|
||||
contest_id: contestID,
|
||||
|
||||
@@ -40,7 +40,7 @@ const columns: DataTableColumn<Contest>[] = [
|
||||
h(
|
||||
NTag,
|
||||
{ type: CONTEST_STATUS[row.status]["type"], size: "small" },
|
||||
() => CONTEST_STATUS[row.status]["name"]
|
||||
() => CONTEST_STATUS[row.status]["name"],
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ async function addProblem() {
|
||||
await addProblemForContest(
|
||||
props.contestID,
|
||||
props.problemID,
|
||||
displayID.value
|
||||
displayID.value,
|
||||
)
|
||||
emit("added")
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -47,7 +47,7 @@ async function getList() {
|
||||
query.limit,
|
||||
query.keyword,
|
||||
"",
|
||||
"ACM"
|
||||
"ACM",
|
||||
)
|
||||
total.value = res.total
|
||||
problems.value = res.results
|
||||
@@ -56,7 +56,7 @@ watch(
|
||||
() => props.show,
|
||||
(value) => {
|
||||
if (value) getList()
|
||||
}
|
||||
},
|
||||
)
|
||||
watch(query, getList, { deep: true })
|
||||
</script>
|
||||
|
||||
@@ -33,7 +33,7 @@ const title = computed(
|
||||
"admin problem edit": "编辑题目",
|
||||
"admin contest problem create": "新建比赛题目",
|
||||
"admin contest problem edit": "编辑比赛题目",
|
||||
}[<string>route.name])
|
||||
})[<string>route.name],
|
||||
)
|
||||
const problem = reactive<BlankProblem>({
|
||||
_id: "",
|
||||
@@ -91,7 +91,7 @@ const languageOptions = [
|
||||
]
|
||||
|
||||
const tagOptions = computed(() =>
|
||||
existingTags.value.map((tag) => ({ label: tag.name, value: tag.name }))
|
||||
existingTags.value.map((tag) => ({ label: tag.name, value: tag.name })),
|
||||
)
|
||||
|
||||
async function getProblemDetail() {
|
||||
@@ -233,7 +233,7 @@ function detectProblemCompletion() {
|
||||
// 样例是空的
|
||||
else if (
|
||||
problem.samples.some(
|
||||
(sample) => sample.output === "" || sample.input === ""
|
||||
(sample) => sample.output === "" || sample.input === "",
|
||||
)
|
||||
) {
|
||||
message.error("空样例没有删干净")
|
||||
|
||||
@@ -20,10 +20,10 @@ const title = computed(
|
||||
({
|
||||
"admin problem list": "题目列表",
|
||||
"admin contest problem list": "比赛题目列表",
|
||||
}[<string>route.name])
|
||||
})[<string>route.name],
|
||||
)
|
||||
const isContestProblemList = computed(
|
||||
() => route.name === "admin contest problem list"
|
||||
() => route.name === "admin contest problem list",
|
||||
)
|
||||
|
||||
const [show, toggleShow] = useToggle()
|
||||
@@ -77,7 +77,7 @@ async function listProblems() {
|
||||
offset,
|
||||
query.limit,
|
||||
query.keyword,
|
||||
props.contestID
|
||||
props.contestID,
|
||||
)
|
||||
total.value = res.total
|
||||
problems.value = res.results
|
||||
|
||||
@@ -28,7 +28,7 @@ const testcaseColumns: DataTableColumn<Testcase>[] = [
|
||||
h(
|
||||
NButton,
|
||||
{ size: "small", onClick: () => deleteTestcase(row.id) },
|
||||
() => "删除"
|
||||
() => "删除",
|
||||
),
|
||||
},
|
||||
]
|
||||
@@ -48,7 +48,7 @@ const serverColumns: DataTableColumn<Server>[] = [
|
||||
h(
|
||||
NTag,
|
||||
{ type: statusMap[row.status].color, size: "small" },
|
||||
() => statusMap[row.status].label
|
||||
() => statusMap[row.status].label,
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -63,7 +63,7 @@ const serverColumns: DataTableColumn<Server>[] = [
|
||||
disabled: row.status === "normal",
|
||||
onClick: () => delJudgeServer(row.hostname),
|
||||
},
|
||||
() => "删除"
|
||||
() => "删除",
|
||||
),
|
||||
},
|
||||
{ title: "主机", key: "hostname", width: 130 },
|
||||
@@ -94,7 +94,7 @@ const testcases = ref<Testcase[]>([])
|
||||
const token = ref("")
|
||||
const servers = ref<Server[]>([])
|
||||
const abnormalServers = computed(() =>
|
||||
servers.value.filter((item) => item.status === "abnormal")
|
||||
servers.value.filter((item) => item.status === "abnormal"),
|
||||
)
|
||||
|
||||
const websiteConfig = reactive({
|
||||
@@ -144,7 +144,7 @@ async function delJudgeServer(hostname: string) {
|
||||
|
||||
async function deleteAbnormalServers() {
|
||||
const dels = abnormalServers.value.map((item) =>
|
||||
deleteJudgeServer(item.hostname)
|
||||
deleteJudgeServer(item.hostname),
|
||||
)
|
||||
await Promise.all(dels)
|
||||
message.success("删除成功")
|
||||
|
||||
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -24,6 +24,7 @@ declare module 'vue' {
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
|
||||
NCode: typeof import('naive-ui')['NCode']
|
||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDatePicker: typeof import('naive-ui')['NDatePicker']
|
||||
|
||||
@@ -10,7 +10,7 @@ const router = useRouter()
|
||||
const learnStore = useLearnStore()
|
||||
|
||||
const Mds = Array.from({ length: learnStore.total }, (_, i) => i + 1).map((v) =>
|
||||
defineAsyncComponent(() => import(`./step-${v}/index.md`))
|
||||
defineAsyncComponent(() => import(`./step-${v}/index.md`)),
|
||||
)
|
||||
|
||||
watch(
|
||||
@@ -24,7 +24,7 @@ watch(
|
||||
router.replace("/learn/step-1")
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export function getWebsiteConfig() {
|
||||
export async function getProblemList(
|
||||
offset = 0,
|
||||
limit = 10,
|
||||
searchParams: any = {}
|
||||
searchParams: any = {},
|
||||
) {
|
||||
let params: any = {
|
||||
paging: true,
|
||||
@@ -129,7 +129,7 @@ export async function getContestProblems(contestID: string) {
|
||||
|
||||
export function getContestRank(
|
||||
contestID: string,
|
||||
query: { limit: number; offset: number; force_refresh: "1" | "0" }
|
||||
query: { limit: number; offset: number; force_refresh: "1" | "0" },
|
||||
) {
|
||||
return http.get("contest_rank", {
|
||||
params: {
|
||||
|
||||
@@ -24,7 +24,7 @@ const passwordFormVisible = computed(
|
||||
() =>
|
||||
contestStore.isPrivate &&
|
||||
!contestStore.access &&
|
||||
!contestStore.isContestAdmin
|
||||
!contestStore.isContestAdmin,
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const columns: DataTableColumn<Contest>[] = [
|
||||
h(
|
||||
NTag,
|
||||
{ type: CONTEST_STATUS[row.status]["type"] },
|
||||
() => CONTEST_STATUS[row.status]["name"]
|
||||
() => CONTEST_STATUS[row.status]["name"],
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -95,13 +95,13 @@ watch(
|
||||
() => {
|
||||
query.page = 1
|
||||
routerPush()
|
||||
}
|
||||
},
|
||||
)
|
||||
watch(
|
||||
() => route.name === "contests" && route.query,
|
||||
(newVal) => {
|
||||
if (newVal) listContests()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function rowProps(row: Contest) {
|
||||
|
||||
@@ -33,7 +33,7 @@ const { resume, pause } = useIntervalFn(
|
||||
10000,
|
||||
{
|
||||
immediate: false,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const query = reactive({
|
||||
@@ -64,7 +64,7 @@ const columns = ref<DataTableColumn<ContestRank>[]>([
|
||||
type: "info",
|
||||
onClick: () => router.push("/user?name=" + row.user.username),
|
||||
},
|
||||
() => row.user.username
|
||||
() => row.user.username,
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -119,7 +119,7 @@ async function addColumns() {
|
||||
window.open(data.href, "_blank")
|
||||
},
|
||||
},
|
||||
() => problem.title
|
||||
() => problem.title,
|
||||
),
|
||||
render: (row) => {
|
||||
if (row.submission_info[problem.id]) {
|
||||
@@ -134,7 +134,7 @@ async function addColumns() {
|
||||
h(
|
||||
NIcon,
|
||||
{ size: 16, style: "transform: translate(-2px, 3px)" },
|
||||
() => h(GoldMedal)
|
||||
() => h(GoldMedal),
|
||||
),
|
||||
secondsToDuration(status.ac_time),
|
||||
]
|
||||
@@ -143,7 +143,7 @@ async function addColumns() {
|
||||
errorNumber = h(
|
||||
"p",
|
||||
{ style: "margin: 0" },
|
||||
`(-${status.error_number})`
|
||||
`(-${status.error_number})`,
|
||||
)
|
||||
}
|
||||
return h("div", [acTime, errorNumber])
|
||||
@@ -182,7 +182,7 @@ watch(
|
||||
() => {
|
||||
query.page = 1
|
||||
listRanks()
|
||||
}
|
||||
},
|
||||
)
|
||||
watch(autoRefresh, (checked) => (checked ? resume() : pause()))
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const contestID = !!route.params.contestID ? route.params.contestID : null
|
||||
|
||||
const storageKey = computed(
|
||||
() =>
|
||||
`problem_${problem.value!._id}_contest_${contestID}_lang_${code.language}`
|
||||
`problem_${problem.value!._id}_contest_${contestID}_lang_${code.language}`,
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
@@ -26,7 +26,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
const editorHeight = computed(() =>
|
||||
isDesktop.value ? "calc(100vh - 133px)" : "calc(100vh - 172px)"
|
||||
isDesktop.value ? "calc(100vh - 133px)" : "calc(100vh - 172px)",
|
||||
)
|
||||
|
||||
function changeCode(v: string) {
|
||||
|
||||
@@ -23,7 +23,7 @@ const samples = ref<Sample[]>(
|
||||
msg: "",
|
||||
status: "not_test",
|
||||
loading: false,
|
||||
}))
|
||||
})),
|
||||
)
|
||||
|
||||
async function test(sample: Sample, index: number) {
|
||||
@@ -202,7 +202,8 @@ function type(status: ProblemStatus) {
|
||||
border: 1px solid rgb(239, 239, 245);
|
||||
word-break: break-word;
|
||||
box-sizing: border-box;
|
||||
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transition:
|
||||
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@@ -219,13 +220,15 @@ function type(status: ProblemStatus) {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
border: 1px solid rgba(239, 239, 245, 1);
|
||||
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transition:
|
||||
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.problemContent > .content > table th {
|
||||
background-color: rgba(250, 250, 252, 1);
|
||||
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transition:
|
||||
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +1,100 @@
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from "~/shared/store/user"
|
||||
import { Submission } from "~/utils/types"
|
||||
import { parseTime } from "~/utils/functions"
|
||||
import { LANGUAGE_SHOW_VALUE } from "~/utils/constants"
|
||||
import { getSubmissions } from "~/oj/api"
|
||||
import { isDesktop } from "~/shared/composables/breakpoints"
|
||||
import SubmissionResultTag from "~/shared/SubmissionResultTag.vue"
|
||||
import Pagination from "~/shared/Pagination.vue"
|
||||
import { NButton } from "naive-ui"
|
||||
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const columns: DataTableColumn<Submission>[] = [
|
||||
{
|
||||
title: "提交时间",
|
||||
key: "create_time",
|
||||
width: 200,
|
||||
render: (row) =>
|
||||
parseTime(
|
||||
row.create_time,
|
||||
isDesktop ? "YYYY-MM-DD HH:mm:ss" : "M-D hh:mm"
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "编号",
|
||||
key: "id",
|
||||
minWidth: 160,
|
||||
render: (row) => {
|
||||
if (row.show_link) {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
type: "info",
|
||||
onClick: () => {
|
||||
const data = router.resolve("/submission/" + row.id)
|
||||
window.open(data.href, "_blank")
|
||||
},
|
||||
},
|
||||
() => row.id.slice(0, 12)
|
||||
)
|
||||
} else {
|
||||
return row.id.slice(0, 12)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
key: "status",
|
||||
width: 140,
|
||||
render: (row) => h(SubmissionResultTag, { result: row.result }),
|
||||
},
|
||||
{
|
||||
title: "语言",
|
||||
key: "language",
|
||||
width: 100,
|
||||
render: (row) => LANGUAGE_SHOW_VALUE[row.language],
|
||||
},
|
||||
]
|
||||
|
||||
const submissions = ref<Submission[]>([])
|
||||
const total = ref(0)
|
||||
const query = reactive({
|
||||
limit: 10,
|
||||
page: 1,
|
||||
})
|
||||
|
||||
async function listSubmissions() {
|
||||
const offset = query.limit * (query.page - 1)
|
||||
const res = await getSubmissions({
|
||||
...query,
|
||||
myself: "1",
|
||||
offset,
|
||||
problem_id: <string>route.params.problemID ?? "",
|
||||
contest_id: <string>route.params.contestID ?? "",
|
||||
})
|
||||
submissions.value = res.data.results
|
||||
total.value = res.data.total
|
||||
}
|
||||
onMounted(listSubmissions)
|
||||
watch(query, listSubmissions)
|
||||
</script>
|
||||
<template>
|
||||
<n-data-table
|
||||
v-if="userStore.isAuthed"
|
||||
striped
|
||||
:columns="columns"
|
||||
:data="submissions"
|
||||
/>
|
||||
<Pagination
|
||||
v-if="userStore.isAuthed"
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
v-model:page="query.page"
|
||||
/>
|
||||
<n-alert type="error" v-if="!userStore.isAuthed" title="请先登录" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from "~/shared/store/user"
|
||||
import { Submission } from "~/utils/types"
|
||||
import { parseTime } from "~/utils/functions"
|
||||
import { LANGUAGE_SHOW_VALUE } from "~/utils/constants"
|
||||
import { getSubmissions } from "~/oj/api"
|
||||
import { isDesktop } from "~/shared/composables/breakpoints"
|
||||
import SubmissionResultTag from "~/shared/SubmissionResultTag.vue"
|
||||
import Pagination from "~/shared/Pagination.vue"
|
||||
import { NButton } from "naive-ui"
|
||||
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const columns: DataTableColumn<Submission>[] = [
|
||||
{
|
||||
title: "提交时间",
|
||||
key: "create_time",
|
||||
width: 200,
|
||||
render: (row) =>
|
||||
parseTime(
|
||||
row.create_time,
|
||||
isDesktop ? "YYYY-MM-DD HH:mm:ss" : "M-D hh:mm",
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "编号",
|
||||
key: "id",
|
||||
minWidth: 160,
|
||||
render: (row) => {
|
||||
if (row.show_link) {
|
||||
return h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
type: "info",
|
||||
onClick: () => {
|
||||
const data = router.resolve("/submission/" + row.id)
|
||||
window.open(data.href, "_blank")
|
||||
},
|
||||
},
|
||||
() => row.id.slice(0, 12),
|
||||
)
|
||||
} else {
|
||||
return row.id.slice(0, 12)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "状态",
|
||||
key: "status",
|
||||
width: 140,
|
||||
render: (row) => h(SubmissionResultTag, { result: row.result }),
|
||||
},
|
||||
{
|
||||
title: "语言",
|
||||
key: "language",
|
||||
width: 100,
|
||||
render: (row) => LANGUAGE_SHOW_VALUE[row.language],
|
||||
},
|
||||
]
|
||||
|
||||
const submissions = ref<Submission[]>([])
|
||||
const total = ref(0)
|
||||
const query = reactive({
|
||||
limit: 10,
|
||||
page: 1,
|
||||
})
|
||||
|
||||
async function listSubmissions() {
|
||||
const offset = query.limit * (query.page - 1)
|
||||
const res = await getSubmissions({
|
||||
...query,
|
||||
myself: "1",
|
||||
offset,
|
||||
problem_id: <string>route.params.problemID ?? "",
|
||||
contest_id: <string>route.params.contestID ?? "",
|
||||
})
|
||||
submissions.value = res.data.results
|
||||
total.value = res.data.total
|
||||
}
|
||||
onMounted(listSubmissions)
|
||||
watch(query, listSubmissions)
|
||||
</script>
|
||||
<template>
|
||||
<n-data-table
|
||||
v-if="userStore.isAuthed"
|
||||
striped
|
||||
:columns="columns"
|
||||
:data="submissions"
|
||||
/>
|
||||
<Pagination
|
||||
v-if="userStore.isAuthed"
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
v-model:page="query.page"
|
||||
/>
|
||||
<n-alert type="error" v-if="!userStore.isAuthed" title="请先登录" />
|
||||
</template>
|
||||
|
||||
@@ -39,17 +39,21 @@ const { start: fetchSubmission } = useTimeoutFn(
|
||||
}
|
||||
},
|
||||
2000,
|
||||
{ immediate: false }
|
||||
{ immediate: false },
|
||||
)
|
||||
|
||||
const judging = computed(
|
||||
() =>
|
||||
!!(submission.value && submission.value.result === SubmissionStatus.judging)
|
||||
!!(
|
||||
submission.value && submission.value.result === SubmissionStatus.judging
|
||||
),
|
||||
)
|
||||
|
||||
const pending = computed(
|
||||
() =>
|
||||
!!(submission.value && submission.value.result === SubmissionStatus.pending)
|
||||
!!(
|
||||
submission.value && submission.value.result === SubmissionStatus.pending
|
||||
),
|
||||
)
|
||||
|
||||
const submitting = computed(
|
||||
@@ -57,7 +61,7 @@ const submitting = computed(
|
||||
!!(
|
||||
submission.value &&
|
||||
submission.value.result === SubmissionStatus.submitting
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
const submitDisabled = computed(() => {
|
||||
@@ -186,7 +190,7 @@ watch(
|
||||
size: party.variation.skew(2, 0.3),
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { code } from "oj/composables/code"
|
||||
import { createTestSubmission } from "~/utils/judge"
|
||||
|
||||
const input = ref("")
|
||||
const output = ref("")
|
||||
|
||||
async function test() {
|
||||
output.value = "运行中..."
|
||||
const res = await createTestSubmission(code, input.value)
|
||||
output.value = res.output
|
||||
}
|
||||
function clear() {
|
||||
const id = setTimeout(() => {
|
||||
clearTimeout(id)
|
||||
output.value = ""
|
||||
}, 200)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n-popover
|
||||
trigger="click"
|
||||
placement="bottom-end"
|
||||
scrollable
|
||||
:show-arrow="false"
|
||||
style="max-height: 600px; max-width: 800px"
|
||||
@clickoutside="clear"
|
||||
>
|
||||
<template #trigger>
|
||||
<n-button>快速测试</n-button>
|
||||
</template>
|
||||
<n-space vertical>
|
||||
<n-input type="textarea" v-model:value="input" />
|
||||
<n-space justify="end">
|
||||
<n-button @click="test">运行</n-button>
|
||||
</n-space>
|
||||
<div class="testcase">{{ output }}</div>
|
||||
</n-space>
|
||||
</n-popover>
|
||||
</template>
|
||||
<style scoped>
|
||||
.testcase {
|
||||
white-space: pre;
|
||||
font-size: 16px;
|
||||
font-family: "Consolas";
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
import { code } from "oj/composables/code"
|
||||
import { createTestSubmission } from "~/utils/judge"
|
||||
|
||||
const input = ref("")
|
||||
const output = ref("")
|
||||
|
||||
async function test() {
|
||||
output.value = "运行中..."
|
||||
const res = await createTestSubmission(code, input.value)
|
||||
output.value = res.output
|
||||
}
|
||||
function clear() {
|
||||
const id = setTimeout(() => {
|
||||
clearTimeout(id)
|
||||
output.value = ""
|
||||
}, 200)
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<n-popover
|
||||
trigger="click"
|
||||
placement="bottom-end"
|
||||
scrollable
|
||||
:show-arrow="false"
|
||||
style="max-height: 600px; max-width: 800px"
|
||||
@clickoutside="clear"
|
||||
>
|
||||
<template #trigger>
|
||||
<n-button>快速测试</n-button>
|
||||
</template>
|
||||
<n-space vertical>
|
||||
<n-input type="textarea" v-model:value="input" />
|
||||
<n-space justify="end">
|
||||
<n-button @click="test">运行</n-button>
|
||||
</n-space>
|
||||
<div class="testcase">{{ output }}</div>
|
||||
</n-space>
|
||||
</n-popover>
|
||||
</template>
|
||||
<style scoped>
|
||||
.testcase {
|
||||
white-space: pre;
|
||||
font-size: 16px;
|
||||
font-family: "Consolas";
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Pagination from "~/shared/Pagination.vue"
|
||||
import { NSpace, NTag } from "naive-ui"
|
||||
import ProblemStatus from "./components/ProblemStatus.vue"
|
||||
import { getProblemTagList } from "~/shared/api"
|
||||
import { isDesktop } from "~/shared/composables/breakpoints"
|
||||
|
||||
interface Tag {
|
||||
id: number
|
||||
@@ -36,6 +37,7 @@ const userStore = useUserStore()
|
||||
const problems = ref<ProblemFiltered[]>([])
|
||||
const total = ref(0)
|
||||
const tags = ref<Tag[]>([])
|
||||
const [showTag, toggleShowTag] = useToggle(isDesktop.value)
|
||||
|
||||
const query = reactive<Query>({
|
||||
keyword: <string>route.query.keyword ?? "",
|
||||
@@ -112,14 +114,14 @@ watch(
|
||||
() => {
|
||||
query.page = 1
|
||||
routerPush()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => route.path === "/" && route.query,
|
||||
(newVal) => {
|
||||
if (newVal) listProblems()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// TODO: 这里会在登录时候执行两次,有BUG
|
||||
@@ -130,7 +132,7 @@ onMounted(() => {
|
||||
listTags()
|
||||
})
|
||||
|
||||
const columns: DataTableColumn<ProblemFiltered>[] = [
|
||||
const baseColumns: DataTableColumn<ProblemFiltered>[] = [
|
||||
{
|
||||
title: "状态",
|
||||
key: "status",
|
||||
@@ -157,6 +159,12 @@ const columns: DataTableColumn<ProblemFiltered>[] = [
|
||||
{ title: "通过率", key: "rate", width: 100 },
|
||||
]
|
||||
|
||||
const columns = computed(() =>
|
||||
userStore.isAuthed
|
||||
? baseColumns
|
||||
: baseColumns.filter((c: any) => c.key !== "status"),
|
||||
)
|
||||
|
||||
function rowProps(row: ProblemFiltered) {
|
||||
return {
|
||||
style: "cursor: pointer",
|
||||
@@ -191,20 +199,22 @@ function rowProps(row: ProblemFiltered) {
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
<n-button @click="toggleShowTag()" quaternary>标签</n-button>
|
||||
</n-space>
|
||||
<n-space>
|
||||
<div class="tagTitle">标签</div>
|
||||
<n-button
|
||||
@click="chooseTag(tag)"
|
||||
v-for="tag in tags"
|
||||
:key="tag.id"
|
||||
size="small"
|
||||
secondary
|
||||
:type="tag.checked ? 'success' : 'default'"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
<n-collapse-transition :show="showTag">
|
||||
<n-space>
|
||||
<n-button
|
||||
@click="chooseTag(tag)"
|
||||
v-for="tag in tags"
|
||||
:key="tag.id"
|
||||
size="small"
|
||||
secondary
|
||||
:type="tag.checked ? 'success' : 'default'"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</n-collapse-transition>
|
||||
<n-data-table
|
||||
striped
|
||||
:data="problems"
|
||||
|
||||
@@ -47,7 +47,7 @@ const columns: DataTableColumn<Rank>[] = [
|
||||
type: "info",
|
||||
onClick: () => router.push("/user?name=" + row.user.username),
|
||||
},
|
||||
() => row.user.username
|
||||
() => row.user.username,
|
||||
),
|
||||
},
|
||||
{ title: "自我介绍", key: "mood", minWidth: 200 },
|
||||
@@ -67,7 +67,7 @@ watch(
|
||||
() => {
|
||||
query.page = 1
|
||||
listRanks()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
onMounted(listRanks)
|
||||
|
||||
@@ -48,11 +48,11 @@ export const useContestStore = defineStore("contest", () => {
|
||||
() =>
|
||||
userStore.isSuperAdmin ||
|
||||
(userStore.isAuthed &&
|
||||
contest.value?.created_by.id === userStore.user!.id)
|
||||
contest.value?.created_by.id === userStore.user!.id),
|
||||
)
|
||||
|
||||
const isPrivate = computed(
|
||||
() => contest.value!.contest_type === ContestType.private
|
||||
() => contest.value!.contest_type === ContestType.private,
|
||||
)
|
||||
|
||||
async function init(contestID: string) {
|
||||
|
||||
@@ -102,7 +102,7 @@ watch(
|
||||
() => {
|
||||
query.page = 1
|
||||
routerPush()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
@@ -111,7 +111,7 @@ watch(
|
||||
route.query,
|
||||
(newVal) => {
|
||||
if (newVal) listSubmissions()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const columns = computed(() => {
|
||||
@@ -123,7 +123,7 @@ const columns = computed(() => {
|
||||
render: (row) =>
|
||||
parseTime(
|
||||
row.create_time,
|
||||
isDesktop ? "YYYY-MM-DD HH:mm:ss" : "M-D hh:mm"
|
||||
isDesktop ? "YYYY-MM-DD HH:mm:ss" : "M-D hh:mm",
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -139,7 +139,7 @@ const columns = computed(() => {
|
||||
type: "info",
|
||||
onClick: () => router.push("/submission/" + row.id),
|
||||
},
|
||||
() => row.id.slice(0, 12)
|
||||
() => row.id.slice(0, 12),
|
||||
)
|
||||
} else {
|
||||
return row.id.slice(0, 12)
|
||||
@@ -175,7 +175,7 @@ const columns = computed(() => {
|
||||
}
|
||||
},
|
||||
},
|
||||
() => row.problem
|
||||
() => row.problem,
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -208,7 +208,7 @@ const columns = computed(() => {
|
||||
type: "info",
|
||||
onClick: () => router.push("/user?name=" + row.username),
|
||||
},
|
||||
() => row.username
|
||||
() => row.username,
|
||||
),
|
||||
},
|
||||
]
|
||||
@@ -225,7 +225,7 @@ const columns = computed(() => {
|
||||
type: "primary",
|
||||
onClick: () => rejudge(row.id),
|
||||
},
|
||||
() => "重新判题"
|
||||
() => "重新判题",
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ watch(
|
||||
() => props.modelValue,
|
||||
(v) => {
|
||||
code.value = v
|
||||
}
|
||||
},
|
||||
)
|
||||
const emit = defineEmits(["update:modelValue"])
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ interface Props {
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const isPrivate = computed(
|
||||
() => props.contest.contest_type === ContestType.private
|
||||
() => props.contest.contest_type === ContestType.private,
|
||||
)
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ import { isDark, toggleDark } from "~/shared/composables/dark"
|
||||
import { toggleLogin, toggleSignup } from "~/shared/composables/modal"
|
||||
import { RouterLink } from "vue-router"
|
||||
import { isDesktop, isMobile } from "~/shared/composables/breakpoints"
|
||||
import { screenSwitchLabel, switchScreenMode } from "~/shared/composables/switchScreen"
|
||||
import {
|
||||
screenSwitchLabel,
|
||||
switchScreenMode,
|
||||
} from "~/shared/composables/switchScreen"
|
||||
import { code } from "~/shared/composables/learn"
|
||||
import { useLearnStore } from "~/learn/store"
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export function loadChart() {
|
||||
Colors,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
Legend,
|
||||
)
|
||||
isLoaded.value = true
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ const options: MenuOption[] = [
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/problem/list" },
|
||||
{ default: () => "题目列表" }
|
||||
{ default: () => "题目列表" },
|
||||
),
|
||||
key: "admin problem list",
|
||||
},
|
||||
@@ -35,7 +35,7 @@ const options: MenuOption[] = [
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/problem/create" },
|
||||
{ default: () => "新建题目" }
|
||||
{ default: () => "新建题目" },
|
||||
),
|
||||
key: "admin problem create",
|
||||
},
|
||||
@@ -50,7 +50,7 @@ const options: MenuOption[] = [
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/user/generate" },
|
||||
{ default: () => "批量生成" }
|
||||
{ default: () => "批量生成" },
|
||||
),
|
||||
key: "admin user generate",
|
||||
},
|
||||
@@ -60,7 +60,7 @@ const options: MenuOption[] = [
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/contest/list" },
|
||||
{ default: () => "比赛列表" }
|
||||
{ default: () => "比赛列表" },
|
||||
),
|
||||
key: "admin contest list",
|
||||
},
|
||||
@@ -69,7 +69,7 @@ const options: MenuOption[] = [
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/contest/create" },
|
||||
{ default: () => "新建比赛" }
|
||||
{ default: () => "新建比赛" },
|
||||
),
|
||||
key: "admin contest create",
|
||||
},
|
||||
@@ -84,7 +84,7 @@ const options: MenuOption[] = [
|
||||
h(
|
||||
RouterLink,
|
||||
{ to: "/admin/announcement" },
|
||||
{ default: () => "公告配置" }
|
||||
{ default: () => "公告配置" },
|
||||
),
|
||||
key: "admin announcement",
|
||||
},
|
||||
|
||||
@@ -11,13 +11,13 @@ export const useUserStore = defineStore("user", () => {
|
||||
const isAdminRole = computed(
|
||||
() =>
|
||||
user.value?.admin_type === USER_TYPE.ADMIN ||
|
||||
user.value?.admin_type === USER_TYPE.SUPER_ADMIN
|
||||
user.value?.admin_type === USER_TYPE.SUPER_ADMIN,
|
||||
)
|
||||
const isSuperAdmin = computed(
|
||||
() => user.value?.admin_type === USER_TYPE.SUPER_ADMIN
|
||||
() => user.value?.admin_type === USER_TYPE.SUPER_ADMIN,
|
||||
)
|
||||
const hasProblemPermission = computed(
|
||||
() => user.value?.problem_permission !== PROBLEM_PERMISSION.NONE
|
||||
() => user.value?.problem_permission !== PROBLEM_PERMISSION.NONE,
|
||||
)
|
||||
|
||||
async function getMyProfile() {
|
||||
|
||||
@@ -100,7 +100,7 @@ export const createTheme = ({
|
||||
},
|
||||
{
|
||||
dark: variant === "dark",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
const highlightStyle = HighlightStyle.define(styles)
|
||||
|
||||
@@ -92,7 +92,7 @@ const oneDarkTheme = EditorView.theme(
|
||||
},
|
||||
},
|
||||
},
|
||||
{ dark: true }
|
||||
{ dark: true },
|
||||
)
|
||||
|
||||
/// The highlighting style for code in the One Dark theme.
|
||||
|
||||
@@ -12,7 +12,7 @@ async function download(url: string) {
|
||||
const headers = res.headers
|
||||
const link = document.createElement("a")
|
||||
link.href = window.URL.createObjectURL(
|
||||
new window.Blob([res.data], { type: headers["content-type"] })
|
||||
new window.Blob([res.data], { type: headers["content-type"] }),
|
||||
)
|
||||
link.download = (headers["content-disposition"] || "").split("filename=")[1]
|
||||
document.body.appendChild(link)
|
||||
|
||||
@@ -18,7 +18,7 @@ export function filterEmptyValue(object: any) {
|
||||
}
|
||||
|
||||
export function getTagColor(
|
||||
tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难"
|
||||
tag: "Low" | "Mid" | "High" | "简单" | "中等" | "困难",
|
||||
) {
|
||||
return <"success" | "info" | "error">{
|
||||
Low: "success",
|
||||
@@ -39,7 +39,7 @@ export function parseTime(utc: Date | string, format = "YYYY年M月D日") {
|
||||
export function duration(
|
||||
start: Date | string,
|
||||
end: Date | string,
|
||||
showSeconds = false
|
||||
showSeconds = false,
|
||||
): string {
|
||||
const duration = intervalToDuration({
|
||||
start: getTime(parseISO(start.toString())),
|
||||
@@ -141,8 +141,8 @@ export function decode(bytes?: string) {
|
||||
const latin = atob(bytes ?? "")
|
||||
return new TextDecoder("utf-8").decode(
|
||||
Uint8Array.from({ length: latin.length }, (_, index) =>
|
||||
latin.charCodeAt(index)
|
||||
)
|
||||
latin.charCodeAt(index),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ http.interceptors.response.use(
|
||||
},
|
||||
(err) => {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
export default http
|
||||
|
||||
Reference in New Issue
Block a user