style(题目): 题目页照设计稿「机房(方案 B)」重排——顶满的两栏、带底色的上下文条、下划线页签、新的结果和题面样式
Deploy / deploy (push) Canceled after 0s
Deploy / deploy (push) Canceled after 0s
原来实施时只照了设计文档,没对照设计稿,一路套 Naive 现成的分段页签、提示框、单选组, 功能对了、样子差很多。这次逐块对着设计稿的画板改: - 两栏顶满内容区,中间一条 1px 分隔线;上下文条、页签行、工具栏下面各一道分隔线 - 上下文条:课堂浅绿、题单浅蓝、比赛浅黄三种底色;‹ › 是不带边框的箭头;比赛的题号是方角、 做错的是红色 ✕;手机上不摆一排题号,写「这节课 做完 2/5 · 这是第 3 道」配 44px 的箭头 - 「题目 / 结果」是下划线页签,右边「统计 / 点评 / 我的提交」是胶囊按钮 - 抽屉盖住整个左栏(连上下文条、页签行),顶上有自己的「我的提交 / 统计 / 点评」页签,Esc 能关 - 结果页签:三个分段是胶囊,选中的深色实心、带时间(「提交结果 · 10:42」);标题是圆形图标 + 大字 + 灰字(「答案错误 在例子 1 上就错了」),不再套整块彩色提示框;答错的对比三栏并排、 只把不一样的那几个字标红;「题目要求的输出」是一行小字;「让 AI 分析我的代码」是描边按钮; 答对后的「下一题」是浅绿卡片里 44px 的大按钮 - 工具栏:语言、运行例子(描边)、提交(实心,就叫「提交」)靠左,求助、⋯ 推到最右; 交对之后「提交」变成绿色描边;协作条换成设计稿的深蓝 - 题面:标题下「简单 · 还没交过」,流程图过关的加「流程图 A 级」;代码要求压成一行; 小标题不带图标;输入、输出左右两栏;参考流程图是一行可以点开的条;例子表带「例子 1」和「复制」 - 去掉「限时 1 秒」:执行时间、内存对中职学生只会添迷惑(和设计稿不同,用户定的) - 我的提交:一整块带框的列表,结果用彩色文字;展开的代码先露 5 行;题单锁住的几次合成一行 - 错在隐藏测试点时「去自己输入」给的数跳过例子里出现过的 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
83b6b7a81a
commit
1fd9bf67c2
17 files changed
+1800
-706
No files matched your search
@@ -8,7 +8,6 @@ import {
|
||||
submissionCaseResults,
|
||||
submissionMemoryFormat,
|
||||
submissionPartialCases,
|
||||
submissionResultTitle,
|
||||
submissionTimeFormat,
|
||||
} from "utils/functions"
|
||||
import type { Submission } from "utils/types"
|
||||
@@ -19,6 +18,7 @@ import PythonErrorExplain from "./PythonErrorExplain.vue"
|
||||
import RuntimeErrorExplain from "./RuntimeErrorExplain.vue"
|
||||
import WrongAnswerExplain from "./WrongAnswerExplain.vue"
|
||||
import LessonNext from "./LessonNext.vue"
|
||||
import ResultHeader from "./ResultHeader.vue"
|
||||
import SimilarProblems from "./SimilarProblems.vue"
|
||||
import { useLessonStore } from "oj/store/lesson"
|
||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||
@@ -151,34 +151,70 @@ const failedOnSample = computed(() =>
|
||||
)
|
||||
|
||||
/**
|
||||
* 结果标题(设计文档第 6 节)。比「答案错误 · 通过 1/3 个测试点」多走一步:
|
||||
* 例子上就错了的说「在例子 N 上就错了」,例子都对的才报测试点;做对了说全对了。
|
||||
* 结果标题(设计文档第 6 节、设计稿「答案错误 / 答案正确 / 错在隐藏测试点」):
|
||||
* 大字是判题结果,灰色小字说清楚错在哪 —— 例子上就错了的说「在例子 N 上就错了」、不挂进度;
|
||||
* 其余照旧报「通过 x/y 个测试点」(一个都没过就不报);做对了说全对了。
|
||||
* 提交详情页还用通用的 submissionResultTitle
|
||||
*/
|
||||
const title = computed(() => {
|
||||
const header = computed(() => {
|
||||
const submission = props.submission
|
||||
if (!submission) return ""
|
||||
if (submission.result === SubmissionStatus.accepted) return "答案正确 · 所有测试点都对了"
|
||||
if (!submission) return null
|
||||
const status = JUDGE_STATUS[submission.result]
|
||||
if (
|
||||
submission.result === SubmissionStatus.pending ||
|
||||
submission.result === SubmissionStatus.judging ||
|
||||
submission.result === SubmissionStatus.submitting
|
||||
) {
|
||||
return { kind: "pending" as const, title: status.title, sub: "", progress: null }
|
||||
}
|
||||
if (submission.result === SubmissionStatus.accepted) {
|
||||
return {
|
||||
kind: "success" as const,
|
||||
title: status.title,
|
||||
sub: "所有测试点都对了",
|
||||
progress: null,
|
||||
}
|
||||
}
|
||||
if (failedOnSample.value) {
|
||||
return `答案错误 · 在例子 ${(failedOnSample.value.index ?? 0) + 1} 上就错了`
|
||||
return {
|
||||
kind: status.type,
|
||||
title: status.title,
|
||||
sub: `在例子 ${(failedOnSample.value.index ?? 0) + 1} 上就错了`,
|
||||
progress: null,
|
||||
}
|
||||
}
|
||||
const cases = partialCases.value
|
||||
return {
|
||||
kind: status.type,
|
||||
title: status.title,
|
||||
sub: cases ? `通过 ${cases.passed}/${cases.total} 个测试点` : "",
|
||||
progress: cases,
|
||||
}
|
||||
const cases = hiddenCases.value
|
||||
if (cases) return `答案错误 · 通过 ${cases.passed}/${cases.total} 个测试点`
|
||||
return submissionResultTitle(submission)
|
||||
})
|
||||
|
||||
/**
|
||||
* 例子都对了、错在隐藏的测试点上:一个都没过也要报「通过 0/5」—— 那正说明例子之外的全错了。
|
||||
* 其余情况沿用 submissionPartialCases(一个都没过就不报)
|
||||
* 例子都对了、错在隐藏测试点:「去『自己输入』拿 90、80 这些数试试」。数取题目描述里出现的、
|
||||
* 例子里没有的头两个 —— 分段、判断一类题的边界多半就写在描述里(设计稿「错在隐藏测试点」)。
|
||||
* SQL 题、比赛里没有「自己输入」
|
||||
*/
|
||||
const hiddenCases = computed(() => {
|
||||
const summary = props.submission?.caseSummary
|
||||
if (!sampleCheck.value?.passed || !summary || summary.passed >= summary.total) return null
|
||||
return summary
|
||||
const submissionStore = useSubmissionStore()
|
||||
const customRunHint = computed(() => {
|
||||
if (!sampleCheck.value?.passed || props.peer) return ""
|
||||
const problem = problemStore.problem
|
||||
if (!problem || problem.sqlConfig) return ""
|
||||
// 例子里出现过的数不算:例子已经对了,拿它们再试一遍没用
|
||||
const inSamples = new Set(
|
||||
problem.samples.flatMap(
|
||||
(sample) => `${sample.input} ${sample.output}`.match(/-?\d+(?:\.\d+)?/g) ?? [],
|
||||
),
|
||||
)
|
||||
const numbers = [...new Set(problem.description.match(/-?\d+(?:\.\d+)?/g) ?? [])]
|
||||
.filter((value) => !inSamples.has(value))
|
||||
.slice(0, 2)
|
||||
return numbers.length
|
||||
? `去「自己输入」拿 ${numbers.join("、")} 这些数试试 ›`
|
||||
: "去「自己输入」拿特殊的数试试 ›"
|
||||
})
|
||||
const progressCases = computed(
|
||||
() => hiddenCases.value ?? (failedOnSample.value ? null : partialCases.value),
|
||||
)
|
||||
|
||||
// 是否显示AI提示区域。
|
||||
// 阈值和后端 POST /ai/hint 共用契约里的 HINT_MIN_FAILURES,别在这里写死数字;
|
||||
@@ -249,16 +285,13 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
|
||||
<template>
|
||||
<div v-if="submission">
|
||||
<n-alert :type="JUDGE_STATUS[submission.result]['type']" :title="title" class="mb-3">
|
||||
<template v-if="progressCases" #default>
|
||||
<n-progress
|
||||
type="line"
|
||||
status="success"
|
||||
:percentage="(progressCases.passed / progressCases.total) * 100"
|
||||
:show-indicator="false"
|
||||
/>
|
||||
</template>
|
||||
</n-alert>
|
||||
<ResultHeader
|
||||
v-if="header"
|
||||
:kind="header.kind"
|
||||
:title="header.title"
|
||||
:sub="header.sub"
|
||||
:progress="header.progress"
|
||||
/>
|
||||
<LessonNext
|
||||
v-if="showLessonNext && problemStore.problem"
|
||||
:problem-display-id="problemStore.problem._id"
|
||||
@@ -310,18 +343,33 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
<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-button
|
||||
v-if="!hintTarget && !hintLoading"
|
||||
type="primary"
|
||||
@click="fetchHint(submission.id)"
|
||||
>
|
||||
让 AI 分析我的代码
|
||||
</n-button>
|
||||
<n-spin v-else-if="hintLoading && !hintTarget" size="small" />
|
||||
<!--
|
||||
设计稿:还没问过 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" />
|
||||
<MdPreview
|
||||
v-if="hintContent"
|
||||
:model-value="hintContent"
|
||||
@@ -379,6 +427,23 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.hint-error {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.hint-card {
|
||||
margin-top: 12px;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.msg {
|
||||
white-space: pre;
|
||||
word-break: break-all;
|
||||
|
||||
Reference in new issue
Block a user