借小丑牌结算时一张张牌亮起来:结果里每组测试点一个方块,从左往右依次亮, 对的绿、错的红并写组号,不到 1 秒亮完,提示和按钮亮完才出来,全对时亮完再放烟花。 大字和「通过几个」判完马上就有;只播刚交的那条,刷新、看旧提交、系统减少动画都直接全亮。 提交详情多给每组对没对(只有对错,不带数据),比赛照旧不给。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1a173f75d7
commit
36b3cd6b21
8 files changed
+375
-144
No files matched your search
@@ -372,8 +372,12 @@ function caseSummary(submission: typeof schema.submission.$inferSelect) {
|
||||
if (submission.contestId !== null || submission.language === "SQL") return null
|
||||
const data = asRecord(submission.info).data
|
||||
if (!Array.isArray(data) || data.length === 0) return null
|
||||
const passed = data.filter((item) => asRecord(item).result === JudgeStatus.ACCEPTED).length
|
||||
return { passed, total: data.length }
|
||||
// 判题时已经按组号排过(judge/run.ts),这里再排一次:老数据不一定是排好的
|
||||
const results = data
|
||||
.map((item) => asRecord(item))
|
||||
.sort((a, b) => Number(a.test_case) - Number(b.test_case))
|
||||
.map((item) => item.result === JudgeStatus.ACCEPTED)
|
||||
return { passed: results.filter(Boolean).length, total: results.length, results }
|
||||
}
|
||||
|
||||
/** 列表行的 caseSummary:SQL 里数好的两个数,为 null 的情形和详情一致(SQL 题、没有逐点结果) */
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import { caseRevealStep } from "../utils/caseReveal"
|
||||
|
||||
/**
|
||||
* 每组测试点一个方块:对的绿、错的红并写上第几组(设计稿「测试点亮起来」)。
|
||||
* `animate` 时从左往右一格一格亮,亮完发 done;不播就一下全亮、马上发 done。
|
||||
* 只在挂上的那一刻看 animate —— 播到一半它变了也不打断。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
results: boolean[]
|
||||
animate: boolean
|
||||
}>()
|
||||
const emit = defineEmits<{ (e: "done"): void }>()
|
||||
|
||||
const theme = useThemeVars()
|
||||
const shown = ref(props.animate ? 0 : props.results.length)
|
||||
const playing = ref(props.animate)
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
onMounted(() => {
|
||||
if (!props.animate) {
|
||||
emit("done")
|
||||
return
|
||||
}
|
||||
timer = setInterval(() => {
|
||||
shown.value++
|
||||
if (shown.value >= props.results.length) {
|
||||
stop()
|
||||
emit("done")
|
||||
}
|
||||
}, caseRevealStep(props.results.length))
|
||||
})
|
||||
function stop() {
|
||||
if (timer) clearInterval(timer)
|
||||
timer = null
|
||||
playing.value = false
|
||||
}
|
||||
onBeforeUnmount(stop)
|
||||
|
||||
const failed = computed(() => props.results.flatMap((passed, i) => (passed ? [] : [i + 1])))
|
||||
const label = computed(() => {
|
||||
const passed = props.results.length - failed.value.length
|
||||
const wrong = failed.value.length ? `,第 ${failed.value.join("、")} 组错了` : ""
|
||||
return `${props.results.length} 组测试点通过 ${passed} 组${wrong}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="blocks" role="img" :aria-label="label">
|
||||
<span
|
||||
v-for="(passed, i) in results"
|
||||
:key="i"
|
||||
class="block"
|
||||
:class="{
|
||||
wait: i >= shown,
|
||||
ok: i < shown && passed,
|
||||
bad: i < shown && !passed,
|
||||
now: playing && i === shown - 1,
|
||||
}"
|
||||
>{{ i < shown && !passed ? i + 1 : "" }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.blocks {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.block.wait {
|
||||
background-color: v-bind("theme.dividerColor");
|
||||
}
|
||||
|
||||
.block.ok {
|
||||
background-color: v-bind("theme.successColor");
|
||||
}
|
||||
|
||||
.block.bad {
|
||||
background-color: v-bind("theme.errorColor");
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 刚亮的那格外面一圈,看得出「亮到哪了」 */
|
||||
.block.now {
|
||||
box-shadow:
|
||||
0 0 0 2px v-bind("theme.cardColor"),
|
||||
0 0 0 4px v-bind("theme.successColorSuppl");
|
||||
}
|
||||
|
||||
.block.now.bad {
|
||||
box-shadow:
|
||||
0 0 0 2px v-bind("theme.cardColor"),
|
||||
0 0 0 4px v-bind("theme.errorColorSuppl");
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import CaseBlocks from "./CaseBlocks.vue"
|
||||
|
||||
/**
|
||||
* 结果页签里每一块结果的标题:圆形状态图标 + 大字 + 灰色小字,下面可以挂一条测试点进度。
|
||||
@@ -12,7 +13,15 @@ const props = defineProps<{
|
||||
sub?: string
|
||||
/** 测试点进度:通过几个、一共几个 */
|
||||
progress?: { passed: number; total: number } | null
|
||||
/**
|
||||
* 每组对没对:有就画一排方块代替上面那根进度条(设计稿「测试点亮起来」),
|
||||
* 没有(老接口、SQL 题)才退回进度条
|
||||
*/
|
||||
cases?: boolean[] | null
|
||||
/** 方块一格一格亮;不亮就一下全亮 */
|
||||
animate?: boolean
|
||||
}>()
|
||||
const emit = defineEmits<{ (e: "revealed"): void }>()
|
||||
|
||||
const theme = useThemeVars()
|
||||
|
||||
@@ -90,8 +99,15 @@ const color = computed(() => {
|
||||
<span v-if="sub" class="sub">{{ sub }}</span>
|
||||
<slot name="extra" />
|
||||
</div>
|
||||
<CaseBlocks
|
||||
v-if="cases && cases.length"
|
||||
:results="cases"
|
||||
:animate="!!animate"
|
||||
class="cases"
|
||||
@done="emit('revealed')"
|
||||
/>
|
||||
<div
|
||||
v-if="progress && progress.total > 0"
|
||||
v-else-if="progress && progress.total > 0"
|
||||
class="progress"
|
||||
role="img"
|
||||
:aria-label="`通过 ${progress.passed}/${progress.total} 个测试点`"
|
||||
@@ -141,6 +157,10 @@ const color = computed(() => {
|
||||
color: v-bind("theme.textColor3");
|
||||
}
|
||||
|
||||
.cases {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useLessonStore } from "shared/store/lesson"
|
||||
import { isFlowchartPass, useFlowchartStore } from "oj/store/flowchart"
|
||||
import { SubmissionStatus } from "utils/constants"
|
||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||
import { caseRevealDuration } from "../utils/caseReveal"
|
||||
|
||||
/**
|
||||
* 判完之后「该发生什么」:失败计数、标成已解决、烟花、点评、回题单。
|
||||
@@ -222,6 +223,20 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* 这一条要播「测试点一格一格亮」的话,等它亮完。判完那一刻只有 WebSocket 推来的结果,
|
||||
* 方块要等详情(带每组对错)回来才开始亮,所以先等详情,最多 3 秒
|
||||
*/
|
||||
async function afterCasesRevealed(id: string | undefined) {
|
||||
if (!submissionStore.shouldRevealCases(id)) return
|
||||
await until(() => submission.value?.id === id && submission.value?.code !== undefined).toBe(
|
||||
true,
|
||||
{ timeout: 3000 },
|
||||
)
|
||||
const count = submission.value?.caseSummary?.results?.length ?? 0
|
||||
if (count) await new Promise((resolve) => setTimeout(resolve, caseRevealDuration(count)))
|
||||
}
|
||||
|
||||
// ==================== AC庆祝效果 ====================
|
||||
watch(
|
||||
() => submission.value?.result,
|
||||
@@ -238,14 +253,17 @@ watch(
|
||||
|
||||
if (result !== SubmissionStatus.accepted) return
|
||||
|
||||
// 2. 放烟花
|
||||
const fireworks = celebrate()
|
||||
|
||||
// 3. 烟花放完弹出评价框。比赛里不打扰;题单里 1.5 秒后要跳回题单页,弹了也会被冲掉
|
||||
if (ctx.value.reviewAfterAccepted) {
|
||||
reviewOwed.value = true
|
||||
showCommentPanelAfter(fireworks)
|
||||
}
|
||||
// (评价是欠着的这件事马上记下:方块还在亮的时候离开这道题,也要先评)
|
||||
if (ctx.value.reviewAfterAccepted) reviewOwed.value = true
|
||||
|
||||
// 2. 放烟花:等结果页签里的方块一格一格亮完(不到 1 秒)再放
|
||||
const id = submission.value?.id
|
||||
void afterCasesRevealed(id).then(() => {
|
||||
if (submission.value?.id !== id) return
|
||||
const fireworks = celebrate()
|
||||
if (ctx.value.reviewAfterAccepted) showCommentPanelAfter(fireworks)
|
||||
})
|
||||
|
||||
if (ctx.value.backToProblemSet) {
|
||||
// 延迟回到题单页面
|
||||
|
||||
@@ -214,12 +214,15 @@ const header = computed(() => {
|
||||
) {
|
||||
return { kind: "pending" as const, title: status.title, sub: "", progress: null }
|
||||
}
|
||||
// 每组对没对:一排方块代替进度条(设计稿「测试点亮起来」),全对也画
|
||||
const cases = submission.caseSummary?.results ?? null
|
||||
if (submission.result === SubmissionStatus.accepted) {
|
||||
return {
|
||||
kind: "success" as const,
|
||||
title: status.title,
|
||||
sub: "所有测试点都对了",
|
||||
progress: null,
|
||||
cases,
|
||||
}
|
||||
}
|
||||
if (failedOnSample.value) {
|
||||
@@ -229,17 +232,42 @@ const header = computed(() => {
|
||||
// 和提交列表右栏同一句,「题目里的」是为了和测试点的编号分开
|
||||
sub: `题目里的例子 ${(failedOnSample.value.index ?? 0) + 1} 就错了`,
|
||||
progress: null,
|
||||
cases,
|
||||
}
|
||||
}
|
||||
const cases = partialCases.value
|
||||
const partial = partialCases.value
|
||||
return {
|
||||
kind: status.type,
|
||||
title: status.title,
|
||||
sub: cases ? `通过 ${cases.passed}/${cases.total} 个测试点` : "",
|
||||
progress: cases,
|
||||
sub: partial ? `通过 ${partial.passed}/${partial.total} 个测试点` : "",
|
||||
progress: partial,
|
||||
cases,
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 方块一格一格亮的时候,下面的提示、按钮、「下一题」先不出来,亮完一起出来(设计稿「测试点亮起来」)。
|
||||
* 判完那一刻只有 WebSocket 推来的结果,详情(带每组对错)还在路上 —— 这段也先等着,
|
||||
* 不然下面的东西会先闪出来、方块开始亮时又缩回去
|
||||
*/
|
||||
const revealedId = ref<string | null>(null)
|
||||
const waitingReveal = computed(() => {
|
||||
const submission = props.submission
|
||||
if (!submission || props.peer || !header.value || header.value.kind === "pending") return false
|
||||
if (!submissionStore.shouldRevealCases(submission.id)) return false
|
||||
if (submission.code === undefined) return true
|
||||
return !!header.value.cases?.length && revealedId.value !== submission.id
|
||||
})
|
||||
const animateCases = computed(
|
||||
() => !props.peer && submissionStore.shouldRevealCases(props.submission?.id),
|
||||
)
|
||||
function onCasesRevealed() {
|
||||
const id = props.submission?.id
|
||||
if (!id) return
|
||||
revealedId.value = id
|
||||
submissionStore.markCasesRevealed(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* 例子都对了、错在隐藏测试点:「去『自己输入』拿 90、80 这些数试试」。数取题目描述里出现的、
|
||||
* 例子里没有的头两个 —— 分段、判断一类题的边界多半就写在描述里(设计稿「错在隐藏测试点」)。
|
||||
@@ -352,152 +380,160 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
<template v-else>
|
||||
<ResultHeader
|
||||
v-if="header"
|
||||
:key="submission.id"
|
||||
:kind="header.kind"
|
||||
:title="header.title"
|
||||
:sub="header.sub"
|
||||
:progress="header.progress"
|
||||
:cases="header.cases"
|
||||
:animate="animateCases"
|
||||
@revealed="onCasesRevealed"
|
||||
/>
|
||||
<LessonNext
|
||||
v-if="showLessonNext && problemStore.problem"
|
||||
:problem-display-id="problemStore.problem._id"
|
||||
:language="submission.language"
|
||||
:refresh-key="submission.id"
|
||||
:challenges="ctx.challenges"
|
||||
/>
|
||||
<!-- 不在这节课里(题库入口自己做的):做对之后先给这道题的挑战,再给相似题 -->
|
||||
<ChallengeList
|
||||
v-if="showSimilar && ctx.challenges && problemStore.problem"
|
||||
:problem-ids="[problemStore.problem._id]"
|
||||
:language="submission.language"
|
||||
:refresh-key="submission.id"
|
||||
/>
|
||||
<SimilarProblems v-if="showSimilar" title="再练几道相似的" />
|
||||
<div v-show="!waitingReveal">
|
||||
<LessonNext
|
||||
v-if="showLessonNext && problemStore.problem"
|
||||
:problem-display-id="problemStore.problem._id"
|
||||
:language="submission.language"
|
||||
:refresh-key="submission.id"
|
||||
:challenges="ctx.challenges"
|
||||
/>
|
||||
<!-- 不在这节课里(题库入口自己做的):做对之后先给这道题的挑战,再给相似题 -->
|
||||
<ChallengeList
|
||||
v-if="showSimilar && ctx.challenges && problemStore.problem"
|
||||
:problem-ids="[problemStore.problem._id]"
|
||||
:language="submission.language"
|
||||
:refresh-key="submission.id"
|
||||
/>
|
||||
<SimilarProblems v-if="showSimilar" title="再练几道相似的" />
|
||||
</div>
|
||||
</template>
|
||||
<n-flex
|
||||
vertical
|
||||
v-if="
|
||||
compileError ||
|
||||
runtimeError ||
|
||||
sampleCheck ||
|
||||
msg ||
|
||||
infoTable.length ||
|
||||
submission.statisticInfo?.ast_results?.length
|
||||
"
|
||||
>
|
||||
<CompileErrorExplain
|
||||
v-if="compileError"
|
||||
:err-info="compileError"
|
||||
:language="submission.language"
|
||||
/>
|
||||
<RuntimeErrorExplain v-if="runtimeError" :info="runtimeError" :code="submission.code" />
|
||||
<WrongAnswerExplain
|
||||
v-if="sampleCheck"
|
||||
full
|
||||
:check="sampleCheck"
|
||||
:code="submission.code"
|
||||
:language="submission.language"
|
||||
/>
|
||||
<n-card v-if="submission.statisticInfo?.ast_results?.length" embedded>
|
||||
<n-flex vertical :size="8">
|
||||
<n-flex
|
||||
v-for="(rule, i) in submission.statisticInfo.ast_results"
|
||||
:key="i"
|
||||
align="center"
|
||||
:size="6"
|
||||
>
|
||||
<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>
|
||||
<!-- 次数类规则光说「出现 2 次 ✗」,学生不知道自己写了几次 -->
|
||||
<span
|
||||
v-if="!rule.passed && rule.actual !== undefined"
|
||||
:style="{ color: theme.errorColor }"
|
||||
<div v-show="!waitingReveal">
|
||||
<n-flex
|
||||
vertical
|
||||
v-if="
|
||||
compileError ||
|
||||
runtimeError ||
|
||||
sampleCheck ||
|
||||
msg ||
|
||||
infoTable.length ||
|
||||
submission.statisticInfo?.ast_results?.length
|
||||
"
|
||||
>
|
||||
<CompileErrorExplain
|
||||
v-if="compileError"
|
||||
:err-info="compileError"
|
||||
:language="submission.language"
|
||||
/>
|
||||
<RuntimeErrorExplain v-if="runtimeError" :info="runtimeError" :code="submission.code" />
|
||||
<WrongAnswerExplain
|
||||
v-if="sampleCheck"
|
||||
full
|
||||
:check="sampleCheck"
|
||||
:code="submission.code"
|
||||
:language="submission.language"
|
||||
/>
|
||||
<n-card v-if="submission.statisticInfo?.ast_results?.length" embedded>
|
||||
<n-flex vertical :size="8">
|
||||
<n-flex
|
||||
v-for="(rule, i) in submission.statisticInfo.ast_results"
|
||||
:key="i"
|
||||
align="center"
|
||||
:size="6"
|
||||
>
|
||||
当前 {{ rule.actual }} 次
|
||||
</span>
|
||||
<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>
|
||||
<!-- 次数类规则光说「出现 2 次 ✗」,学生不知道自己写了几次 -->
|
||||
<span
|
||||
v-if="!rule.passed && rule.actual !== undefined"
|
||||
:style="{ color: theme.errorColor }"
|
||||
>
|
||||
当前 {{ rule.actual }} 次
|
||||
</span>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</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-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-flex>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
设计稿:还没问过 AI 时只是一个描边按钮;错在隐藏测试点时旁边给「去自己输入试试」。
|
||||
问过之后,AI 的回答才放进卡片
|
||||
-->
|
||||
<div v-if="(showAIHint && !hintTarget) || customRunHint" class="actions">
|
||||
<n-button
|
||||
v-if="showAIHint && !hintTarget"
|
||||
type="primary"
|
||||
ghost
|
||||
:loading="hintLoading"
|
||||
@click="fetchHint(submission.id)"
|
||||
>
|
||||
让 AI 分析我的代码
|
||||
</n-button>
|
||||
<n-button
|
||||
v-if="customRunHint"
|
||||
text
|
||||
type="primary"
|
||||
@click="submissionStore.revealResult('custom')"
|
||||
>
|
||||
{{ customRunHint }}
|
||||
</n-button>
|
||||
</div>
|
||||
<n-alert v-if="showAIHint && hintError" type="error" :title="hintError" class="hint-error" />
|
||||
<template v-if="showAIHint && hintTarget">
|
||||
<n-card size="small" class="hint-card">
|
||||
<n-spin v-if="hintLoading && !hintContent" size="small" />
|
||||
<Markdown v-if="hintContent" :content="hintContent" />
|
||||
<!-- 等级和「再多一点提示」。按钮出不出由后端 done 里的 canEscalate 定,
|
||||
<div v-if="(showAIHint && !hintTarget) || customRunHint" class="actions">
|
||||
<n-button
|
||||
v-if="showAIHint && !hintTarget"
|
||||
type="primary"
|
||||
ghost
|
||||
:loading="hintLoading"
|
||||
@click="fetchHint(submission.id)"
|
||||
>
|
||||
让 AI 分析我的代码
|
||||
</n-button>
|
||||
<n-button
|
||||
v-if="customRunHint"
|
||||
text
|
||||
type="primary"
|
||||
@click="submissionStore.revealResult('custom')"
|
||||
>
|
||||
{{ customRunHint }}
|
||||
</n-button>
|
||||
</div>
|
||||
<n-alert v-if="showAIHint && hintError" type="error" :title="hintError" class="hint-error" />
|
||||
<template v-if="showAIHint && hintTarget">
|
||||
<n-card size="small" class="hint-card">
|
||||
<n-spin v-if="hintLoading && !hintContent" size="small" />
|
||||
<Markdown v-if="hintContent" :content="hintContent" />
|
||||
<!-- 等级和「再多一点提示」。按钮出不出由后端 done 里的 canEscalate 定,
|
||||
前端不自己推阶梯(要再交一次才升得动,规则在 services/hint-level.ts) -->
|
||||
<n-flex
|
||||
v-if="hintLevel !== null && !hintLoading && !hintTyping"
|
||||
align="center"
|
||||
size="small"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
<n-tag size="small" :bordered="false">
|
||||
{{ hintLevelLabel(hintLevel) }}
|
||||
</n-tag>
|
||||
<n-button
|
||||
v-if="hintCanEscalate"
|
||||
size="tiny"
|
||||
type="primary"
|
||||
ghost
|
||||
@click="fetchHint(submission.id, true)"
|
||||
<n-flex
|
||||
v-if="hintLevel !== null && !hintLoading && !hintTyping"
|
||||
align="center"
|
||||
size="small"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
再多一点提示
|
||||
</n-button>
|
||||
</n-flex>
|
||||
<n-flex
|
||||
v-if="hintId !== null && !hintLoading && !hintTyping"
|
||||
align="center"
|
||||
size="small"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
<n-text depth="3">这条提示对你有帮助吗?</n-text>
|
||||
<n-button
|
||||
size="tiny"
|
||||
:type="hintHelpful === true ? 'primary' : 'default'"
|
||||
:disabled="hintFeedbackSending"
|
||||
@click="sendHintFeedback(true)"
|
||||
<n-tag size="small" :bordered="false">
|
||||
{{ hintLevelLabel(hintLevel) }}
|
||||
</n-tag>
|
||||
<n-button
|
||||
v-if="hintCanEscalate"
|
||||
size="tiny"
|
||||
type="primary"
|
||||
ghost
|
||||
@click="fetchHint(submission.id, true)"
|
||||
>
|
||||
再多一点提示
|
||||
</n-button>
|
||||
</n-flex>
|
||||
<n-flex
|
||||
v-if="hintId !== null && !hintLoading && !hintTyping"
|
||||
align="center"
|
||||
size="small"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
有帮助
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
:type="hintHelpful === false ? 'warning' : 'default'"
|
||||
:disabled="hintFeedbackSending"
|
||||
@click="sendHintFeedback(false)"
|
||||
>
|
||||
没帮助
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
</template>
|
||||
<n-text depth="3">这条提示对你有帮助吗?</n-text>
|
||||
<n-button
|
||||
size="tiny"
|
||||
:type="hintHelpful === true ? 'primary' : 'default'"
|
||||
:disabled="hintFeedbackSending"
|
||||
@click="sendHintFeedback(true)"
|
||||
>
|
||||
有帮助
|
||||
</n-button>
|
||||
<n-button
|
||||
size="tiny"
|
||||
:type="hintHelpful === false ? 'warning' : 'default'"
|
||||
:disabled="hintFeedbackSending"
|
||||
@click="sendHintFeedback(false)"
|
||||
>
|
||||
没帮助
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 测试点一格一格亮(设计稿「测试点亮起来」,借小丑牌结算时一张张牌亮起来)的节奏。
|
||||
* 不管几组,全部亮完不超过 0.9 秒:组少的一格 120 毫秒,组多的平分 900 毫秒。
|
||||
* 结果页签里的方块(CaseBlocks)和等方块亮完再放的烟花(SubmissionEffects)用同一份。
|
||||
*/
|
||||
const TOTAL_MS = 900
|
||||
const STEP_MAX_MS = 120
|
||||
|
||||
export function caseRevealStep(count: number) {
|
||||
return Math.min(STEP_MAX_MS, Math.floor(TOTAL_MS / Math.max(count, 1)))
|
||||
}
|
||||
|
||||
export function caseRevealDuration(count: number) {
|
||||
return caseRevealStep(count) * count
|
||||
}
|
||||
@@ -65,6 +65,25 @@ export const useSubmissionStore = defineStore("submission", () => {
|
||||
revealResult("custom")
|
||||
trial.runCustom({ ...codeStore.code }, { problemId: problem.id, contestId: problem.contestId })
|
||||
}
|
||||
/**
|
||||
* 测试点一格一格亮(结果页签里的 CaseBlocks)只播一次,而且只播这一页上刚交、刚判完的那条:
|
||||
* 刷新、切页签回来、看以前的提交都直接全亮。系统设了「减少动画」的不播。
|
||||
* 烟花(SubmissionEffects)按同一个判断决定要不要等方块亮完
|
||||
*/
|
||||
const reducedMotion = usePreferredReducedMotion()
|
||||
const revealedCases = new Set<string>()
|
||||
function shouldRevealCases(id: string | undefined) {
|
||||
return (
|
||||
!!id &&
|
||||
reducedMotion.value !== "reduce" &&
|
||||
id === monitor.submissionId.value &&
|
||||
!revealedCases.has(id)
|
||||
)
|
||||
}
|
||||
function markCasesRevealed(id: string) {
|
||||
revealedCases.add(id)
|
||||
}
|
||||
|
||||
const isFormatting = ref(false)
|
||||
const isSubmittingRequest = ref(false)
|
||||
|
||||
@@ -206,6 +225,8 @@ export const useSubmissionStore = defineStore("submission", () => {
|
||||
resultSeq,
|
||||
resultSegment,
|
||||
revealResult,
|
||||
shouldRevealCases,
|
||||
markCasesRevealed,
|
||||
runSamples,
|
||||
runCustom,
|
||||
isFormatting,
|
||||
|
||||
@@ -218,6 +218,11 @@ export const caseSummarySchema = z
|
||||
.object({
|
||||
passed: z.number().int(),
|
||||
total: z.number().int(),
|
||||
/**
|
||||
* 每组对没对,按组号排。只有详情给(结果页签里方块一格一格亮,错的写组号),列表只给
|
||||
* 两个数。只有对错、不带数据和输出,所以学生也给;为 null 的情形同上(比赛照旧不给)
|
||||
*/
|
||||
results: z.array(z.boolean()).optional(),
|
||||
})
|
||||
.nullable()
|
||||
|
||||
|
||||
Reference in new issue
Block a user