1 parent
23844ae301
commit
d25b5dbe6d
268 files changed
+3684
-8861
No files matched your search
@@ -20,8 +20,7 @@ const { isDesktop } = useBreakpoints()
|
||||
|
||||
const contestID = route.params.contestID || null
|
||||
const storageKey = computed(
|
||||
() =>
|
||||
`problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
||||
() => `problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
||||
)
|
||||
|
||||
const editorHeight = computed(() =>
|
||||
@@ -35,10 +34,7 @@ onMounted(() => {
|
||||
problem.value!.template[codeStore.code.language] ||
|
||||
SOURCES[codeStore.code.language],
|
||||
)
|
||||
beginEditTrace(
|
||||
`problem_${problem.value!._id}_contest_${contestID}`,
|
||||
codeStore.code.value.length,
|
||||
)
|
||||
beginEditTrace(`problem_${problem.value!._id}_contest_${contestID}`, codeStore.code.value.length)
|
||||
})
|
||||
|
||||
const changeCode = (v: string) => {
|
||||
@@ -50,18 +46,14 @@ const changeLanguage = (v: LANGUAGE) => {
|
||||
codeStore.setCode(
|
||||
savedCode && storageKey.value.split("_").pop() === v
|
||||
? savedCode
|
||||
: problem.value!.template[codeStore.code.language] ||
|
||||
SOURCES[codeStore.code.language],
|
||||
: problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-flex vertical>
|
||||
<EditorToolbar
|
||||
:storage-key="storageKey"
|
||||
@change-language="changeLanguage"
|
||||
/>
|
||||
<EditorToolbar :storage-key="storageKey" @change-language="changeLanguage" />
|
||||
<CodeEditor
|
||||
v-model:value="codeStore.code.value"
|
||||
:language="codeStore.code.language"
|
||||
|
||||
@@ -20,8 +20,7 @@ const { input, output } = storeToRefs(codeStore)
|
||||
const { problem } = storeToRefs(problemStore)
|
||||
|
||||
const storageKey = computed(
|
||||
() =>
|
||||
`problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
||||
() => `problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
@@ -29,8 +28,7 @@ onMounted(() => {
|
||||
codeStore.setCode(storage.get(storageKey.value))
|
||||
} else {
|
||||
codeStore.setCode(
|
||||
problem.value!.template[codeStore.code.language] ||
|
||||
SOURCES[codeStore.code.language],
|
||||
problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -40,15 +38,11 @@ function changeCode(v: string) {
|
||||
}
|
||||
|
||||
function changeLanguage(v: string) {
|
||||
if (
|
||||
storage.get(storageKey.value) &&
|
||||
storageKey.value.split("_").pop() === v
|
||||
) {
|
||||
if (storage.get(storageKey.value) && storageKey.value.split("_").pop() === v) {
|
||||
codeStore.setCode(storage.get(storageKey.value))
|
||||
} else {
|
||||
codeStore.setCode(
|
||||
problem.value!.template[codeStore.code.language] ||
|
||||
SOURCES[codeStore.code.language],
|
||||
problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -60,8 +54,7 @@ const copy = async () => {
|
||||
|
||||
const reset = () => {
|
||||
codeStore.setCode(
|
||||
problem.value!.template[codeStore.code.language] ||
|
||||
SOURCES[codeStore.code.language],
|
||||
problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
||||
)
|
||||
storage.remove(storageKey.value)
|
||||
message.success("代码重置成功")
|
||||
@@ -72,12 +65,10 @@ const runCode = async () => {
|
||||
output.value = res.output
|
||||
}
|
||||
|
||||
const languageOptions: DropdownOption[] = problem.value!.languages.map(
|
||||
(it) => ({
|
||||
label: () => LANGUAGE_SHOW_VALUE[it],
|
||||
value: it,
|
||||
}),
|
||||
)
|
||||
const languageOptions: DropdownOption[] = problem.value!.languages.map((it) => ({
|
||||
label: () => LANGUAGE_SHOW_VALUE[it],
|
||||
value: it,
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -94,9 +85,7 @@ const languageOptions: DropdownOption[] = problem.value!.languages.map(
|
||||
/>
|
||||
<n-button @click="copy">复制代码</n-button>
|
||||
<n-button @click="reset">重置代码</n-button>
|
||||
<n-button type="primary" secondary @click="runCode">
|
||||
运行代码
|
||||
</n-button>
|
||||
<n-button type="primary" secondary @click="runCode"> 运行代码 </n-button>
|
||||
</n-flex>
|
||||
<CodeEditor
|
||||
v-model:value="codeStore.code.value"
|
||||
@@ -106,12 +95,7 @@ const languageOptions: DropdownOption[] = problem.value!.languages.map(
|
||||
</n-flex>
|
||||
</template>
|
||||
<template #2>
|
||||
<n-split
|
||||
direction="vertical"
|
||||
:default-size="1 / 3"
|
||||
:min="1 / 5"
|
||||
:max="3 / 5"
|
||||
>
|
||||
<n-split direction="vertical" :default-size="1 / 3" :min="1 / 5" :max="3 / 5">
|
||||
<template #1>
|
||||
<div class="title">输入框</div>
|
||||
<n-input
|
||||
|
||||
@@ -19,14 +19,10 @@ import { Icon } from "@iconify/vue"
|
||||
import { NFlex } from "naive-ui"
|
||||
import SubmitCode from "./SubmitCode.vue"
|
||||
|
||||
const SubmitFlowchart = defineAsyncComponent(
|
||||
() => import("./SubmitFlowchart.vue"),
|
||||
)
|
||||
const SubmitFlowchart = defineAsyncComponent(() => import("./SubmitFlowchart.vue"))
|
||||
// 只有老师看得见(下面的弹框挂了 isTeacherOrAbove),静态 import 的话每个学生
|
||||
// 打开题目都要白拉一份 chart.js(~68KB gzip)
|
||||
const StatisticsPanel = defineAsyncComponent(
|
||||
() => import("shared/components/StatisticsPanel.vue"),
|
||||
)
|
||||
const StatisticsPanel = defineAsyncComponent(() => import("shared/components/StatisticsPanel.vue"))
|
||||
|
||||
interface Props {
|
||||
storageKey: string
|
||||
@@ -74,13 +70,9 @@ const showHelpButton = computed(
|
||||
* 不像弹框那样按一下 Esc 就把协作关掉了。
|
||||
*/
|
||||
const collabHere = computed(
|
||||
() =>
|
||||
collabStore.room !== null &&
|
||||
collabStore.room.problemId === problem.value?._id,
|
||||
)
|
||||
const showCollabBar = computed(
|
||||
() => collabHere.value && userStore.isTeacherOrAbove,
|
||||
() => collabStore.room !== null && collabStore.room.problemId === problem.value?._id,
|
||||
)
|
||||
const showCollabBar = computed(() => collabHere.value && userStore.isTeacherOrAbove)
|
||||
|
||||
/**
|
||||
* 状态全塞进按钮本身。原来旁边还挂一个 n-tag 说明排队情况,一行工具栏
|
||||
@@ -212,8 +204,7 @@ const copy = async () => {
|
||||
|
||||
const reset = () => {
|
||||
codeStore.setCode(
|
||||
problem.value!.template[codeStore.code.language] ||
|
||||
SOURCES[codeStore.code.language],
|
||||
problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
||||
)
|
||||
storage.remove(storageKey)
|
||||
message.success("代码重置成功")
|
||||
@@ -280,11 +271,7 @@ onMounted(() => {
|
||||
|
||||
<SubmitCode v-else />
|
||||
|
||||
<n-button
|
||||
v-if="isDesktop && showGoSubmissionButton"
|
||||
:size="buttonSize"
|
||||
@click="goSubmissions"
|
||||
>
|
||||
<n-button v-if="isDesktop && showGoSubmissionButton" :size="buttonSize" @click="goSubmissions">
|
||||
提交信息
|
||||
</n-button>
|
||||
|
||||
@@ -313,11 +300,7 @@ onMounted(() => {
|
||||
</n-tag>
|
||||
<!-- 显式的「结束」:求助记录一并清掉。跳走页面发的是 leave("left"),
|
||||
那边只是退回排队 —— 见 store 里 leave 的注释 -->
|
||||
<n-button
|
||||
:size="buttonSize"
|
||||
type="primary"
|
||||
@click="collabStore.leave('done')"
|
||||
>
|
||||
<n-button :size="buttonSize" type="primary" @click="collabStore.leave('done')">
|
||||
结束协作
|
||||
</n-button>
|
||||
</template>
|
||||
|
||||
@@ -22,11 +22,7 @@ watch(
|
||||
<n-alert v-if="renderError" type="error" title="渲染失败" size="small">
|
||||
{{ renderError }}
|
||||
</n-alert>
|
||||
<div
|
||||
v-show="!renderError"
|
||||
ref="mermaidContainer"
|
||||
class="flowchart-container"
|
||||
></div>
|
||||
<div v-show="!renderError" ref="mermaidContainer" class="flowchart-container"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -108,9 +108,7 @@ const KIND_TAG_TYPE = {
|
||||
count: "info",
|
||||
} as const
|
||||
|
||||
const astRequirements = computed(() =>
|
||||
Object.entries(problem.value?.astRequirements ?? {}),
|
||||
)
|
||||
const astRequirements = computed(() => Object.entries(problem.value?.astRequirements ?? {}))
|
||||
|
||||
async function test(sample: Sample, index: number) {
|
||||
samples.value = samples.value.map((sample) => {
|
||||
@@ -122,10 +120,7 @@ async function test(sample: Sample, index: number) {
|
||||
const res = await createTestSubmission(codeStore.code, sample.input)
|
||||
samples.value = samples.value.map((sample) => {
|
||||
if (sample.id === index) {
|
||||
const status =
|
||||
res.status === 3 && res.output.trim() === sample.output
|
||||
? "passed"
|
||||
: "failed"
|
||||
const status = res.status === 3 && res.output.trim() === sample.output ? "passed" : "failed"
|
||||
return {
|
||||
...sample,
|
||||
msg: res.output,
|
||||
@@ -267,9 +262,7 @@ function type(status: ProblemStatus) {
|
||||
:total-rows="sqlExpectedQuery.total_rows"
|
||||
:truncated="sqlExpectedQuery.truncated"
|
||||
/>
|
||||
<p v-if="!problem.sqlConfig?.order_sensitive" class="sqlNote">
|
||||
结果顺序不限
|
||||
</p>
|
||||
<p v-if="!problem.sqlConfig?.order_sensitive" class="sqlNote">结果顺序不限</p>
|
||||
</template>
|
||||
<div v-for="t in sqlChangedTables" :key="t.name">
|
||||
<p class="sqlTableName">
|
||||
@@ -313,9 +306,7 @@ function type(status: ProblemStatus) {
|
||||
</p>
|
||||
<n-list bordered style="margin-bottom: 8px">
|
||||
<n-list-item v-for="(rule, i) in rules" :key="i">
|
||||
<n-tag :type="KIND_TAG_TYPE[rule.kind]">{{
|
||||
rule.description
|
||||
}}</n-tag>
|
||||
<n-tag :type="KIND_TAG_TYPE[rule.kind]">{{ rule.description }}</n-tag>
|
||||
</n-list-item>
|
||||
</n-list>
|
||||
</div>
|
||||
@@ -330,19 +321,11 @@ function type(status: ProblemStatus) {
|
||||
例子 {{ index + 1 }}
|
||||
</n-flex>
|
||||
</p>
|
||||
<n-button
|
||||
size="small"
|
||||
:type="type(sample.status)"
|
||||
@click="test(sample, index)"
|
||||
>
|
||||
<n-button size="small" :type="type(sample.status)" @click="test(sample, index)">
|
||||
{{ label(sample.status, sample.loading) }}
|
||||
</n-button>
|
||||
</n-flex>
|
||||
<n-descriptions
|
||||
bordered
|
||||
:column="2"
|
||||
label-style="width: 50%; min-width: 100px"
|
||||
>
|
||||
<n-descriptions bordered :column="2" label-style="width: 50%; min-width: 100px">
|
||||
<n-descriptions-item>
|
||||
<template #label>
|
||||
<n-flex>
|
||||
|
||||
@@ -31,9 +31,7 @@ const { problem } = storeToRefs(problemStore)
|
||||
* 协作跟着结束」:页面上没有弹框可关,结束协作只有工具栏那个按钮和离开这一页两条路。
|
||||
*/
|
||||
const collabHere = computed(
|
||||
() =>
|
||||
collabStore.room !== null &&
|
||||
collabStore.room.problemId === problem.value?._id,
|
||||
() => collabStore.room !== null && collabStore.room.problemId === problem.value?._id,
|
||||
)
|
||||
|
||||
/** 协作中的教师:编辑器里是学生的代码,不是他自己的 */
|
||||
@@ -66,8 +64,7 @@ watch(
|
||||
const wasHere = previous?.[0] ?? false
|
||||
if (here && language) {
|
||||
if (!wasHere) teacherLanguageBefore = codeStore.code.language
|
||||
if (codeStore.code.language !== language)
|
||||
codeStore.code.language = language
|
||||
if (codeStore.code.language !== language) codeStore.code.language = language
|
||||
return
|
||||
}
|
||||
if (!here && wasHere) {
|
||||
@@ -84,8 +81,7 @@ const { isDesktop } = useBreakpoints()
|
||||
|
||||
const contestID = route.params.contestID || null
|
||||
const storageKey = computed(
|
||||
() =>
|
||||
`problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
||||
() => `problem_${problem.value!._id}_contest_${contestID}_lang_${codeStore.code.language}`,
|
||||
)
|
||||
|
||||
const editorHeight = computed(() =>
|
||||
@@ -100,10 +96,7 @@ function loadCode() {
|
||||
SOURCES[codeStore.code.language],
|
||||
)
|
||||
// 换了题才重新计数,同一道题重复 loadCode(协作结束读回草稿)是接着记
|
||||
beginEditTrace(
|
||||
`problem_${problem.value!._id}_contest_${contestID}`,
|
||||
codeStore.code.value.length,
|
||||
)
|
||||
beginEditTrace(`problem_${problem.value!._id}_contest_${contestID}`, codeStore.code.value.length)
|
||||
}
|
||||
|
||||
onMounted(loadCode)
|
||||
@@ -128,8 +121,7 @@ const changeLanguage = (v: LANGUAGE) => {
|
||||
codeStore.setCode(
|
||||
savedCode && storageKey.value.split("_").pop() === v
|
||||
? savedCode
|
||||
: problem.value!.template[codeStore.code.language] ||
|
||||
SOURCES[codeStore.code.language],
|
||||
: problem.value!.template[codeStore.code.language] || SOURCES[codeStore.code.language],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -139,10 +131,7 @@ provide("flowchartEditorRef", flowchartEditorRef)
|
||||
|
||||
<template>
|
||||
<n-flex vertical>
|
||||
<EditorToolbar
|
||||
:storage-key="storageKey"
|
||||
@change-language="changeLanguage"
|
||||
/>
|
||||
<EditorToolbar :storage-key="storageKey" @change-language="changeLanguage" />
|
||||
<!--
|
||||
协作中教师这边不会落到流程图分支:上面那个 watch 已经把他的语言换成了学生的,
|
||||
而求助入口本身就排掉了流程图(EditorToolbar.vue 的 showHelpButton、服务端的
|
||||
@@ -150,10 +139,7 @@ provide("flowchartEditorRef", flowchartEditorRef)
|
||||
学生自己切到流程图就是不写代码了,编辑器卸载、协作正常结束(SyncCodeEditor
|
||||
的 detach),这是原来就有的语义。
|
||||
-->
|
||||
<FlowchartEditor
|
||||
v-if="codeStore.code.language === 'Flowchart'"
|
||||
ref="flowchartEditorRef"
|
||||
/>
|
||||
<FlowchartEditor v-if="codeStore.code.language === 'Flowchart'" ref="flowchartEditorRef" />
|
||||
<SyncCodeEditor
|
||||
v-else
|
||||
v-model:value="codeStore.code.value"
|
||||
|
||||
@@ -9,10 +9,7 @@ const mermaidContainer = useTemplateRef<HTMLElement>("mermaidContainer")
|
||||
const { renderError, renderFlowchart } = useMermaid()
|
||||
|
||||
const renderProblemFlowchart = async () => {
|
||||
await renderFlowchart(
|
||||
mermaidContainer.value,
|
||||
problem.value?.mermaidCode ?? "",
|
||||
)
|
||||
await renderFlowchart(mermaidContainer.value, problem.value?.mermaidCode ?? "")
|
||||
}
|
||||
|
||||
onMounted(renderProblemFlowchart)
|
||||
|
||||
@@ -6,14 +6,7 @@ import { DIFFICULTY, JUDGE_STATUS } from "utils/constants"
|
||||
import type { SUBMISSION_RESULT } from "utils/types"
|
||||
import { getACRateNumber, getTagColor, parseTime } from "utils/functions"
|
||||
import { Pie } from "vue-chartjs"
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
ArcElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
Colors,
|
||||
} from "chart.js"
|
||||
import { Chart as ChartJS, ArcElement, Title, Tooltip, Legend, Colors } from "chart.js"
|
||||
import { getProblemBeatRate } from "oj/api"
|
||||
import { getProblemYearlyAC, type YearlyACData } from "oj/api"
|
||||
import ProblemYearlyChart from "./ProblemYearlyChart.vue"
|
||||
@@ -106,12 +99,7 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-descriptions
|
||||
bordered
|
||||
label-placement="left"
|
||||
:column="isDesktop ? 3 : 1"
|
||||
v-if="problem"
|
||||
>
|
||||
<n-descriptions bordered label-placement="left" :column="isDesktop ? 3 : 1" v-if="problem">
|
||||
<n-descriptions-item label="编号">
|
||||
{{ problem._id }}
|
||||
</n-descriptions-item>
|
||||
@@ -141,10 +129,7 @@ onMounted(() => {
|
||||
<n-flex vertical align="center">
|
||||
<Icon v-if="isDesktop" :icon="item.icon" width="40" />
|
||||
<n-h2 class="number">
|
||||
<n-number-animation
|
||||
:to="item.title"
|
||||
:precision="item.int ? 0 : 2"
|
||||
/>
|
||||
<n-number-animation :to="item.title" :precision="item.int ? 0 : 2" />
|
||||
<span v-if="item.suffix">{{ item.suffix }}</span>
|
||||
</n-h2>
|
||||
<n-h4 class="number-label">{{ item.content }}</n-h4>
|
||||
|
||||
@@ -9,20 +9,8 @@ defineProps<{
|
||||
<template>
|
||||
<n-flex align="center">
|
||||
<span>{{ problem.title }}</span>
|
||||
<Icon
|
||||
v-if="problem.allowFlowchart"
|
||||
width="18"
|
||||
icon="vscode-icons:file-type-drawio"
|
||||
/>
|
||||
<Icon
|
||||
v-else-if="problem.showFlowchart"
|
||||
width="18"
|
||||
icon="vscode-icons:file-type-graphql"
|
||||
/>
|
||||
<Icon
|
||||
v-if="problem.hasAstRules"
|
||||
width="18"
|
||||
icon="vscode-icons:file-type-light-todo"
|
||||
/>
|
||||
<Icon v-if="problem.allowFlowchart" width="18" icon="vscode-icons:file-type-drawio" />
|
||||
<Icon v-else-if="problem.showFlowchart" width="18" icon="vscode-icons:file-type-graphql" />
|
||||
<Icon v-if="problem.hasAstRules" width="18" icon="vscode-icons:file-type-light-todo" />
|
||||
</n-flex>
|
||||
</template>
|
||||
@@ -54,15 +54,12 @@ function getWheelItemStyle(index: number): CSSProperties {
|
||||
const dividerAngle = index * sliceAngle - sliceAngle / 2
|
||||
const position = pointOnCircle(centerAngle, wheelGeometry.contentRadius)
|
||||
const push = pointOnCircle(centerAngle, wheelGeometry.pushRadius)
|
||||
const arcPoints = Array.from(
|
||||
{ length: wheelGeometry.arcPointCount },
|
||||
(_, pointIndex) => {
|
||||
const progress = pointIndex / (wheelGeometry.arcPointCount - 1)
|
||||
const angle = startAngle + (endAngle - startAngle) * progress
|
||||
const point = pointOnCircle(angle, wheelGeometry.outerRadius)
|
||||
return `${point.x.toFixed(3)}% ${point.y.toFixed(3)}%`
|
||||
},
|
||||
)
|
||||
const arcPoints = Array.from({ length: wheelGeometry.arcPointCount }, (_, pointIndex) => {
|
||||
const progress = pointIndex / (wheelGeometry.arcPointCount - 1)
|
||||
const angle = startAngle + (endAngle - startAngle) * progress
|
||||
const point = pointOnCircle(angle, wheelGeometry.outerRadius)
|
||||
return `${point.x.toFixed(3)}% ${point.y.toFixed(3)}%`
|
||||
})
|
||||
|
||||
return {
|
||||
"--segment-path": `polygon(50% 50%, ${arcPoints.join(", ")})`,
|
||||
@@ -192,9 +189,7 @@ function getPointerIndex(event: PointerEvent | MouseEvent) {
|
||||
|
||||
const angle = (Math.atan2(y, x) * 180) / Math.PI
|
||||
const rawIndex = Math.round((angle - wheelGeometry.startAngle) / sliceAngle)
|
||||
return (
|
||||
((rawIndex % wheelItems.length) + wheelItems.length) % wheelItems.length
|
||||
)
|
||||
return ((rawIndex % wheelItems.length) + wheelItems.length) % wheelItems.length
|
||||
}
|
||||
|
||||
function preview(index: number, fromKeyboard = false) {
|
||||
@@ -225,14 +220,7 @@ function onWheelClick(event: MouseEvent) {
|
||||
}
|
||||
|
||||
async function pick(key: ReactionKey) {
|
||||
if (
|
||||
!problem.value ||
|
||||
!solved.value ||
|
||||
locked.value ||
|
||||
loading.value ||
|
||||
submitting.value
|
||||
)
|
||||
return
|
||||
if (!problem.value || !solved.value || locked.value || loading.value || submitting.value) return
|
||||
|
||||
activeIndex.value = null
|
||||
keyboardActive.value = false
|
||||
@@ -345,18 +333,10 @@ watch(
|
||||
>
|
||||
<span class="option-content">
|
||||
<span class="option-icon" aria-hidden="true">
|
||||
<Icon
|
||||
:icon="
|
||||
submitting === item.key
|
||||
? 'svg-spinners:180-ring-with-bg'
|
||||
: item.icon
|
||||
"
|
||||
/>
|
||||
<Icon :icon="submitting === item.key ? 'svg-spinners:180-ring-with-bg' : item.icon" />
|
||||
</span>
|
||||
<span class="option-label">{{ item.label }}</span>
|
||||
<span v-if="counts" class="option-count">
|
||||
{{ counts[item.key] }} 人
|
||||
</span>
|
||||
<span v-if="counts" class="option-count"> {{ counts[item.key] }} 人 </span>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -420,22 +400,13 @@ watch(
|
||||
|
||||
.segment-face:is(.is-active, .is-submitting) {
|
||||
z-index: 2;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--reaction-accent) 10%,
|
||||
var(--reaction-card)
|
||||
);
|
||||
transform: translate(calc(var(--push-x) * 0.7), calc(var(--push-y) * 0.7))
|
||||
scale(1.045);
|
||||
background: color-mix(in srgb, var(--reaction-accent) 10%, var(--reaction-card));
|
||||
transform: translate(calc(var(--push-x) * 0.7), calc(var(--push-y) * 0.7)) scale(1.045);
|
||||
}
|
||||
|
||||
.segment-face.is-selected {
|
||||
z-index: 3;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--reaction-accent) 18%,
|
||||
var(--reaction-card)
|
||||
);
|
||||
background: color-mix(in srgb, var(--reaction-accent) 18%, var(--reaction-card));
|
||||
transform: translate(var(--push-x), var(--push-y)) scale(1.075);
|
||||
}
|
||||
|
||||
@@ -512,14 +483,11 @@ watch(
|
||||
}
|
||||
|
||||
.reaction-option:is(.is-active, .is-submitting) .option-content {
|
||||
transform: translate(calc(var(--push-x) * 0.8), calc(var(--push-y) * 0.8))
|
||||
scale(1.18);
|
||||
transform: translate(calc(var(--push-x) * 0.8), calc(var(--push-y) * 0.8)) scale(1.18);
|
||||
}
|
||||
|
||||
.reaction-option:is(.is-active, .is-submitting):active:not(:disabled)
|
||||
.option-content {
|
||||
transform: translate(calc(var(--push-x) * 0.8), calc(var(--push-y) * 0.8))
|
||||
scale(1.1);
|
||||
.reaction-option:is(.is-active, .is-submitting):active:not(:disabled) .option-content {
|
||||
transform: translate(calc(var(--push-x) * 0.8), calc(var(--push-y) * 0.8)) scale(1.1);
|
||||
}
|
||||
|
||||
.reaction-option.is-selected .option-content {
|
||||
@@ -623,13 +591,11 @@ watch(
|
||||
}
|
||||
|
||||
.segment-face:is(.is-active, .is-submitting) {
|
||||
transform: translate(calc(var(--push-x) * 0.5), calc(var(--push-y) * 0.5))
|
||||
scale(1.03);
|
||||
transform: translate(calc(var(--push-x) * 0.5), calc(var(--push-y) * 0.5)) scale(1.03);
|
||||
}
|
||||
|
||||
.segment-face.is-selected {
|
||||
transform: translate(calc(var(--push-x) * 0.75), calc(var(--push-y) * 0.75))
|
||||
scale(1.05);
|
||||
transform: translate(calc(var(--push-x) * 0.75), calc(var(--push-y) * 0.75)) scale(1.05);
|
||||
}
|
||||
|
||||
.option-content {
|
||||
@@ -637,19 +603,15 @@ watch(
|
||||
}
|
||||
|
||||
.reaction-option:is(.is-active, .is-submitting) .option-content {
|
||||
transform: translate(calc(var(--push-x) * 0.55), calc(var(--push-y) * 0.55))
|
||||
scale(1.12);
|
||||
transform: translate(calc(var(--push-x) * 0.55), calc(var(--push-y) * 0.55)) scale(1.12);
|
||||
}
|
||||
|
||||
.reaction-option:is(.is-active, .is-submitting):active:not(:disabled)
|
||||
.option-content {
|
||||
transform: translate(calc(var(--push-x) * 0.55), calc(var(--push-y) * 0.55))
|
||||
scale(1.06);
|
||||
.reaction-option:is(.is-active, .is-submitting):active:not(:disabled) .option-content {
|
||||
transform: translate(calc(var(--push-x) * 0.55), calc(var(--push-y) * 0.55)) scale(1.06);
|
||||
}
|
||||
|
||||
.reaction-option.is-selected .option-content {
|
||||
transform: translate(calc(var(--push-x) * 0.75), calc(var(--push-y) * 0.75))
|
||||
scale(1.17);
|
||||
transform: translate(calc(var(--push-x) * 0.75), calc(var(--push-y) * 0.75)) scale(1.17);
|
||||
}
|
||||
|
||||
.option-icon {
|
||||
|
||||
@@ -48,10 +48,7 @@ const columns: DataTableColumn<SubmissionListItem>[] = [
|
||||
NTooltip,
|
||||
{},
|
||||
{
|
||||
trigger: () =>
|
||||
h(NButton, { text: true }, () =>
|
||||
h(Icon, { icon: "catppuccin:lock" }),
|
||||
),
|
||||
trigger: () => h(NButton, { text: true }, () => h(Icon, { icon: "catppuccin:lock" })),
|
||||
default: () =>
|
||||
"这道题在你已经加入的题单里,加入之前的提交先藏起来了。在题单中做出此题即可解锁;题单过了截止时间也会解锁。",
|
||||
},
|
||||
@@ -190,12 +187,7 @@ watch(query, listSubmissions)
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-alert>
|
||||
<n-alert
|
||||
class="tip"
|
||||
type="error"
|
||||
:show-icon="false"
|
||||
v-if="rank === -1 && class_ac_count > 0"
|
||||
>
|
||||
<n-alert class="tip" type="error" :show-icon="false" v-if="rank === -1 && class_ac_count > 0">
|
||||
<template #header>
|
||||
<n-flex align="center">
|
||||
<span>
|
||||
@@ -252,12 +244,7 @@ watch(query, listSubmissions)
|
||||
</n-flex>
|
||||
</template>
|
||||
</n-alert>
|
||||
<n-alert
|
||||
class="tip"
|
||||
type="error"
|
||||
:show-icon="false"
|
||||
v-if="rank === -1 && all_ac_count > 0"
|
||||
>
|
||||
<n-alert class="tip" type="error" :show-icon="false" v-if="rank === -1 && all_ac_count > 0">
|
||||
<template #header>
|
||||
<n-flex align="center">
|
||||
<span>
|
||||
@@ -288,12 +275,7 @@ watch(query, listSubmissions)
|
||||
|
||||
<template v-if="userStore.showSubmissions && userStore.isAuthed">
|
||||
<!-- 错误分布统计 -->
|
||||
<n-flex
|
||||
v-if="statusDistribution.length"
|
||||
class="tip"
|
||||
align="center"
|
||||
:wrap="true"
|
||||
>
|
||||
<n-flex v-if="statusDistribution.length" class="tip" align="center" :wrap="true">
|
||||
<span style="font-weight: bold; font-size: 13px">我的提交统计:</span>
|
||||
<n-tag
|
||||
v-for="item in statusDistribution"
|
||||
@@ -306,17 +288,8 @@ watch(query, listSubmissions)
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
|
||||
<n-data-table
|
||||
v-if="submissions.length > 0"
|
||||
striped
|
||||
:columns="columns"
|
||||
:data="submissions"
|
||||
/>
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:limit="query.limit"
|
||||
v-model:page="query.page"
|
||||
/>
|
||||
<n-data-table v-if="submissions.length > 0" striped :columns="columns" :data="submissions" />
|
||||
<Pagination :total="total" v-model:limit="query.limit" v-model:page="query.page" />
|
||||
</template>
|
||||
|
||||
<!-- 代码详情弹框 -->
|
||||
|
||||
@@ -18,15 +18,7 @@ import {
|
||||
} from "chart.js"
|
||||
import type { YearlyACData } from "oj/api"
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
Filler,
|
||||
LinearScale,
|
||||
LineElement,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
)
|
||||
ChartJS.register(CategoryScale, Filler, LinearScale, LineElement, PointElement, Title, Tooltip)
|
||||
|
||||
const props = defineProps<{ data: YearlyACData[] }>()
|
||||
|
||||
|
||||
@@ -30,9 +30,7 @@ defineProps<{
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
<p v-if="truncated" class="truncNote">
|
||||
共 {{ totalRows }} 行,仅展示前 {{ rows.length }} 行
|
||||
</p>
|
||||
<p v-if="truncated" class="truncNote">共 {{ totalRows }} 行,仅展示前 {{ rows.length }} 行</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -52,9 +52,7 @@ const hintFeedbackSending = ref(false)
|
||||
const TYPE_STEP = 3
|
||||
const TYPE_INTERVAL = 24
|
||||
let typingTimer: ReturnType<typeof setInterval> | null = null
|
||||
const hintTyping = computed(
|
||||
() => hintContent.value.length < hintTarget.value.length,
|
||||
)
|
||||
const hintTyping = computed(() => hintContent.value.length < hintTarget.value.length)
|
||||
|
||||
function stopTyping() {
|
||||
if (typingTimer === null) return
|
||||
@@ -69,10 +67,7 @@ function startTyping() {
|
||||
stopTyping()
|
||||
return
|
||||
}
|
||||
hintContent.value = hintTarget.value.slice(
|
||||
0,
|
||||
hintContent.value.length + TYPE_STEP,
|
||||
)
|
||||
hintContent.value = hintTarget.value.slice(0, hintContent.value.length + TYPE_STEP)
|
||||
}, TYPE_INTERVAL)
|
||||
}
|
||||
|
||||
@@ -99,17 +94,13 @@ const msg = computed(() => {
|
||||
// 编译错误或运行时错误时给出提示;
|
||||
// SQL 题的运行错误多半是"查询题里写了增删改"这类被判题拒绝的语句,err_info 已说明原因,不套这句
|
||||
if (
|
||||
(result === SubmissionStatus.compile_error ||
|
||||
result === SubmissionStatus.runtime_error) &&
|
||||
(result === SubmissionStatus.compile_error || result === SubmissionStatus.runtime_error) &&
|
||||
props.submission.language !== "SQL"
|
||||
) {
|
||||
msg += "请仔细检查,看看代码的格式是不是写错了!\n\n"
|
||||
}
|
||||
|
||||
if (
|
||||
result !== SubmissionStatus.ast_check_failed &&
|
||||
props.submission.statisticInfo?.err_info
|
||||
) {
|
||||
if (result !== SubmissionStatus.ast_check_failed && props.submission.statisticInfo?.err_info) {
|
||||
msg += props.submission.statisticInfo.err_info
|
||||
}
|
||||
|
||||
@@ -258,9 +249,7 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
</n-alert>
|
||||
<n-flex
|
||||
vertical
|
||||
v-if="
|
||||
msg || infoTable.length || submission.statisticInfo?.ast_results?.length
|
||||
"
|
||||
v-if="msg || infoTable.length || submission.statisticInfo?.ast_results?.length"
|
||||
>
|
||||
<n-card v-if="submission.statisticInfo?.ast_results?.length" embedded>
|
||||
<n-flex vertical :size="8">
|
||||
@@ -270,9 +259,7 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
align="center"
|
||||
:size="6"
|
||||
>
|
||||
<n-icon
|
||||
:color="rule.passed ? theme.successColor : theme.errorColor"
|
||||
>
|
||||
<n-icon :color="rule.passed ? theme.successColor : theme.errorColor">
|
||||
<Icon :icon="rule.passed ? 'ph:check-bold' : 'ph:x-bold'" />
|
||||
</n-icon>
|
||||
<span>{{ rule.description }}</span>
|
||||
@@ -287,23 +274,13 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
</n-flex>
|
||||
</n-card>
|
||||
<n-card v-if="msg" embedded class="msg">{{ msg }}</n-card>
|
||||
<n-data-table
|
||||
v-if="infoTable.length"
|
||||
striped
|
||||
:data="infoTable"
|
||||
:columns="columns"
|
||||
/>
|
||||
<n-data-table v-if="infoTable.length" striped :data="infoTable" :columns="columns" />
|
||||
</n-flex>
|
||||
|
||||
<!-- AI 提示区域 -->
|
||||
<template v-if="showAIHint">
|
||||
<n-card size="small" style="margin-top: 12px; max-width: 480px">
|
||||
<n-alert
|
||||
v-if="hintError"
|
||||
type="error"
|
||||
:title="hintError"
|
||||
class="mb-3"
|
||||
/>
|
||||
<n-alert v-if="hintError" type="error" :title="hintError" class="mb-3" />
|
||||
<n-button
|
||||
v-if="!hintTarget && !hintLoading"
|
||||
type="primary"
|
||||
|
||||
@@ -14,20 +14,13 @@ import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
import { useCollabStore } from "shared/store/collab"
|
||||
import { restartEditTrace, snapshotEditTrace } from "oj/problem/utils/editTrace"
|
||||
import {
|
||||
checkPythonSyntax,
|
||||
prefetchPythonSyntaxChecker,
|
||||
} from "oj/problem/utils/pythonSyntaxCheck"
|
||||
import { checkPythonSyntax, prefetchPythonSyntaxChecker } from "oj/problem/utils/pythonSyntaxCheck"
|
||||
|
||||
// ==================== 异步组件 ====================
|
||||
const ProblemReaction = defineAsyncComponent(
|
||||
() => import("./ProblemReaction.vue"),
|
||||
)
|
||||
const ProblemReaction = defineAsyncComponent(() => import("./ProblemReaction.vue"))
|
||||
// 结果面板第一次弹出(也就是第一次提交)时才加载:它带着 DataTable,而判题要等
|
||||
// 好几秒,这点下载时间藏得住。进页面就加载的话,只看题不提交的人也要付这笔
|
||||
const SubmissionResult = defineAsyncComponent(
|
||||
() => import("./SubmissionResult.vue"),
|
||||
)
|
||||
const SubmissionResult = defineAsyncComponent(() => import("./SubmissionResult.vue"))
|
||||
|
||||
// ==================== 基础状态 ====================
|
||||
const userStore = useUserStore()
|
||||
@@ -53,8 +46,7 @@ const { isDesktop } = useBreakpoints()
|
||||
const { celebrate } = useFireworks()
|
||||
|
||||
// ==================== 判题监控 ====================
|
||||
const { submission, judging, pending, submitting, startMonitoring } =
|
||||
useSubmissionMonitor()
|
||||
const { submission, judging, pending, submitting, startMonitoring } = useSubmissionMonitor()
|
||||
|
||||
const showResult = ref(false)
|
||||
const isFormatting = ref(false)
|
||||
@@ -68,8 +60,7 @@ watch(
|
||||
() => codeStore.code.language === "Python" && userStore.isAuthed,
|
||||
(needed) => {
|
||||
if (!needed) return
|
||||
if ("requestIdleCallback" in window)
|
||||
requestIdleCallback(prefetchPythonSyntaxChecker)
|
||||
if ("requestIdleCallback" in window) requestIdleCallback(prefetchPythonSyntaxChecker)
|
||||
else setTimeout(prefetchPythonSyntaxChecker, 1000)
|
||||
},
|
||||
{ immediate: true },
|
||||
@@ -162,8 +153,7 @@ async function submit() {
|
||||
code: codeStore.code.value,
|
||||
// 编辑过程信号,见 utils/editTrace.ts。协作的判断和 ProblemEditor 的 collabHere 同一个口径
|
||||
trace: snapshotEditTrace(
|
||||
collabStore.room !== null &&
|
||||
collabStore.room.problemId === problem.value!._id,
|
||||
collabStore.room !== null && collabStore.room.problemId === problem.value!._id,
|
||||
),
|
||||
}
|
||||
if (contestID) {
|
||||
@@ -219,11 +209,7 @@ watch(
|
||||
watch(
|
||||
() => submission.value?.result,
|
||||
async (result) => {
|
||||
if (
|
||||
result !== SubmissionStatus.accepted &&
|
||||
result !== SubmissionStatus.ast_check_failed
|
||||
)
|
||||
return
|
||||
if (result !== SubmissionStatus.accepted && result !== SubmissionStatus.ast_check_failed) return
|
||||
|
||||
// 1. 刷新题目状态
|
||||
problem.value!.myStatus = 0
|
||||
|
||||
@@ -10,10 +10,7 @@ import { sortFlowchartCriteria } from "utils/constants"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { useMermaid } from "shared/composables/useMermaid"
|
||||
import { useMermaidConverter } from "../composables/useMermaidConverter"
|
||||
import {
|
||||
useFlowchartWebSocket,
|
||||
type FlowchartEvaluationUpdate,
|
||||
} from "shared/composables/websocket"
|
||||
import { useFlowchartWebSocket, type FlowchartEvaluationUpdate } from "shared/composables/websocket"
|
||||
import { useMyFlowchartStore } from "shared/store/myFlowchart"
|
||||
|
||||
// API 和状态管理
|
||||
@@ -46,8 +43,7 @@ interface FlowchartEditorInstance {
|
||||
}
|
||||
|
||||
// 通过inject获取FlowchartEditor组件的引用
|
||||
const flowchartEditorRef =
|
||||
inject<Ref<FlowchartEditorInstance | null>>("flowchartEditorRef")
|
||||
const flowchartEditorRef = inject<Ref<FlowchartEditorInstance | null>>("flowchartEditorRef")
|
||||
const mermaidContainer = useTemplateRef<HTMLElement>("mermaidContainer")
|
||||
|
||||
// 基础组合式函数
|
||||
@@ -77,14 +73,10 @@ const evaluation = ref<Evaluation>({
|
||||
})
|
||||
const page = ref(1)
|
||||
const lastSubmittedMermaidCode = ref("")
|
||||
const suggestionLines = computed(() =>
|
||||
splitSuggestionLines(evaluation.value.suggestions),
|
||||
)
|
||||
const suggestionLines = computed(() => splitSuggestionLines(evaluation.value.suggestions))
|
||||
|
||||
// jsonb 不保留键序,直接遍历会把 40 分的「逻辑正确性」排到最后
|
||||
const sortedCriteria = computed(() =>
|
||||
sortFlowchartCriteria(evaluation.value.criteria_details),
|
||||
)
|
||||
const sortedCriteria = computed(() => sortFlowchartCriteria(evaluation.value.criteria_details))
|
||||
|
||||
function splitSuggestionLines(suggestions?: string | null) {
|
||||
return suggestions
|
||||
@@ -111,9 +103,7 @@ const POLL_INTERVAL = 3000
|
||||
/** 到点还没结果就收手,别无限轮询下去 */
|
||||
const POLL_TIMEOUT = 3 * 60 * 1000
|
||||
|
||||
type Outcome =
|
||||
| { ok: true; score: number; grade: string }
|
||||
| { ok: false; error?: string }
|
||||
type Outcome = { ok: true; score: number; grade: string } | { ok: false; error?: string }
|
||||
|
||||
const { pause: pausePolling, resume: resumePolling } = useIntervalFn(
|
||||
async () => {
|
||||
@@ -175,21 +165,12 @@ function settle(submissionId: string, outcome: Outcome) {
|
||||
loading.value = false
|
||||
|
||||
if (!outcome.ok) {
|
||||
message.error(
|
||||
outcome.error
|
||||
? `流程图评分失败: ${outcome.error}`
|
||||
: "流程图评分失败,请稍后重试",
|
||||
)
|
||||
message.error(outcome.error ? `流程图评分失败: ${outcome.error}` : "流程图评分失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
latestRating.value = { score: outcome.score, grade: outcome.grade }
|
||||
message.success(
|
||||
`流程图评分完成!得分: ${outcome.score}分 (${outcome.grade}级)`,
|
||||
)
|
||||
if (
|
||||
(outcome.grade === "A" || outcome.grade === "S") &&
|
||||
lastSubmittedMermaidCode.value
|
||||
) {
|
||||
message.success(`流程图评分完成!得分: ${outcome.score}分 (${outcome.grade}级)`)
|
||||
if ((outcome.grade === "A" || outcome.grade === "S") && lastSubmittedMermaidCode.value) {
|
||||
myFlowchartStore.show(lastSubmittedMermaidCode.value)
|
||||
}
|
||||
}
|
||||
@@ -208,9 +189,8 @@ const handleWebSocketMessage = (data: FlowchartEvaluationUpdate) => {
|
||||
}
|
||||
|
||||
// 创建 WebSocket 连接
|
||||
const { connect, disconnect, subscribe, unsubscribe } = useFlowchartWebSocket(
|
||||
handleWebSocketMessage,
|
||||
)
|
||||
const { connect, disconnect, subscribe, unsubscribe } =
|
||||
useFlowchartWebSocket(handleWebSocketMessage)
|
||||
|
||||
// 订阅提交更新,同时开启轮询兜底
|
||||
function subscribeToSubmission(submissionId: string) {
|
||||
@@ -292,10 +272,7 @@ async function getCurrentSubmission() {
|
||||
|
||||
async function getSubmission(submissionPage = 0) {
|
||||
if (!problem.value?.id) return
|
||||
const data = await getFlowchartSubmissionDetail(
|
||||
problem.value.id,
|
||||
submissionPage,
|
||||
)
|
||||
const data = await getFlowchartSubmissionDetail(problem.value.id, submissionPage)
|
||||
submissionCount.value = data.count
|
||||
const submission = data.submission
|
||||
// 翻到没有提交的页时后端返回 null(契约里 submission 是 nullable)——
|
||||
@@ -324,8 +301,7 @@ async function getSubmission(submissionPage = 0) {
|
||||
grade: (submission.aiGrade ?? "") as Rating["grade"],
|
||||
feedback: submission.aiFeedback ?? "",
|
||||
suggestions: submission.aiSuggestions ?? "",
|
||||
criteria_details:
|
||||
submission.aiCriteriaDetails as Evaluation["criteria_details"],
|
||||
criteria_details: submission.aiCriteriaDetails as Evaluation["criteria_details"],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,18 +441,12 @@ onUnmounted(() => {
|
||||
<n-alert v-if="renderError" type="error" title="流程图渲染失败">
|
||||
{{ renderError }}
|
||||
</n-alert>
|
||||
<div
|
||||
class="flowchart"
|
||||
v-show="!renderError"
|
||||
ref="mermaidContainer"
|
||||
></div>
|
||||
<div class="flowchart" v-show="!renderError" ref="mermaidContainer"></div>
|
||||
</n-spin>
|
||||
</div>
|
||||
<!-- 加载到编辑器按钮 -->
|
||||
<n-flex style="margin-top: 16px" justify="center">
|
||||
<n-button @click="loadToEditor" type="primary">
|
||||
加载到流程图编辑器
|
||||
</n-button>
|
||||
<n-button @click="loadToEditor" type="primary"> 加载到流程图编辑器 </n-button>
|
||||
</n-flex>
|
||||
</n-gi>
|
||||
|
||||
@@ -511,23 +481,11 @@ onUnmounted(() => {
|
||||
|
||||
<!-- 详细评分 -->
|
||||
<n-card v-if="sortedCriteria.length" size="small" title="详细评分">
|
||||
<div
|
||||
v-for="[key, detail] in sortedCriteria"
|
||||
:key="key"
|
||||
style="margin-bottom: 12px"
|
||||
>
|
||||
<div v-for="[key, detail] in sortedCriteria" :key="key" style="margin-bottom: 12px">
|
||||
<!-- 评分项标题和分数 -->
|
||||
<n-flex
|
||||
justify="space-between"
|
||||
align="center"
|
||||
style="margin-bottom: 4px"
|
||||
>
|
||||
<n-flex justify="space-between" align="center" style="margin-bottom: 4px">
|
||||
<n-text strong>{{ key }}</n-text>
|
||||
<n-tag
|
||||
:type="getPercentType(detail.score / detail.max)"
|
||||
size="small"
|
||||
round
|
||||
>
|
||||
<n-tag :type="getPercentType(detail.score / detail.max)" size="small" round>
|
||||
{{ detail.score || 0 }}分 / {{ detail.max }}分
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
@@ -541,16 +499,8 @@ onUnmounted(() => {
|
||||
</n-grid>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<n-flex
|
||||
justify="center"
|
||||
style="margin-top: 24px"
|
||||
v-if="submissionCount > 1"
|
||||
>
|
||||
<n-pagination
|
||||
v-model:page="page"
|
||||
:page-count="submissionCount"
|
||||
@update-page="updatePage"
|
||||
/>
|
||||
<n-flex justify="center" style="margin-top: 24px" v-if="submissionCount > 1">
|
||||
<n-pagination v-model:page="page" :page-count="submissionCount" @update-page="updatePage" />
|
||||
</n-flex>
|
||||
</n-modal>
|
||||
</n-flex>
|
||||
|
||||
@@ -21,13 +21,7 @@ export function getSubmitButtonState({
|
||||
isJudging,
|
||||
isCooldown,
|
||||
}: SubmitButtonStateInput): SubmitButtonState {
|
||||
const disabled =
|
||||
!isAuthed ||
|
||||
!hasCode ||
|
||||
isFormatting ||
|
||||
isSubmitting ||
|
||||
isJudging ||
|
||||
isCooldown
|
||||
const disabled = !isAuthed || !hasCode || isFormatting || isSubmitting || isJudging || isCooldown
|
||||
|
||||
let label = "提交代码"
|
||||
if (!isAuthed) {
|
||||
|
||||
Reference in new issue
Block a user