From 5836902c6b7a5a1c3900710fb65afab4a791dcbc Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Mon, 28 Sep 2026 23:13:23 +0800 Subject: [PATCH] =?UTF-8?q?style(=E9=A2=98=E7=9B=AE):=20=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E4=BE=8B=E5=AD=90=E3=80=81=E8=87=AA=E5=B7=B1=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E3=80=81Python=20=E6=8A=A5=E9=94=99=E3=80=81=E6=B5=81=E7=A8=8B?= =?UTF-8?q?=E5=9B=BE=E7=82=B9=E8=AF=84=E8=BF=99=E5=87=A0=E5=9D=97=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E4=B9=9F=E7=85=A7=E8=AE=BE=E8=AE=A1=E7=A8=BF=E9=87=8D?= =?UTF-8?q?=E6=8E=92=EF=BC=8C=E6=89=8B=E6=9C=BA=E4=B8=8A=E9=A2=98=E7=9B=AE?= =?UTF-8?q?=E9=A1=B5=E9=A1=B6=E6=BB=A1=E5=B1=8F=E5=B9=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 运行例子:标题带每个例子的对错胶囊,全对时提示「再按提交」 - 自己输入:说明「这里不判对错」,输出框留出固定高度 - Python 报错:灰底卡片,改法按钮放显眼处 - 流程图点评:画过几次连成一排,大分数 + 等级 + 四项分数条,重新提交后直接显示新的这一次 - 工具栏「写代码 / 画流程图」换成设计稿的分段切换,窄时缩成「代码 / 流程图」 - 设计文档记下和画板有意不同的几处(去掉限时、保留新轮盘等) Co-Authored-By: Claude Opus 5.5 --- .../src/oj/problem/components/ContextBar.vue | 2 + .../src/oj/problem/components/CustomRun.vue | 76 +-- .../oj/problem/components/EditorToolbar.vue | 55 ++- .../oj/problem/components/FlowchartResult.vue | 434 +++++++++++------- .../oj/problem/components/MobileActionBar.vue | 2 + .../problem/components/PythonErrorExplain.vue | 113 ++--- .../src/oj/problem/components/ResultPane.vue | 10 + .../oj/problem/components/SampleRunResult.vue | 151 ++++-- .../problem/components/SubmissionResult.vue | 8 + .../oj/problem/components/SubmitFlowchart.vue | 2 +- .../problem/components/WrongAnswerExplain.vue | 8 + apps/web/src/oj/problem/detail.vue | 7 + apps/web/src/oj/store/flowchart.ts | 2 + ...2026-09-28-problem-page-redesign-design.md | 17 +- 14 files changed, 598 insertions(+), 289 deletions(-) diff --git a/apps/web/src/oj/problem/components/ContextBar.vue b/apps/web/src/oj/problem/components/ContextBar.vue index dc08969..0fc1193 100644 --- a/apps/web/src/oj/problem/components/ContextBar.vue +++ b/apps/web/src/oj/problem/components/ContextBar.vue @@ -427,7 +427,9 @@ onSolvedHere(() => { color: v-bind("tone.setText"); } +/* 比赛名不跟题号那段抢地方:题号多了本来就能横向滚,名字挤成两个字就认不出了 */ .contest-back { + flex: none; max-width: 160px; color: v-bind("tone.contestText"); } diff --git a/apps/web/src/oj/problem/components/CustomRun.vue b/apps/web/src/oj/problem/components/CustomRun.vue index 41d8c1e..52c7de6 100644 --- a/apps/web/src/oj/problem/components/CustomRun.vue +++ b/apps/web/src/oj/problem/components/CustomRun.vue @@ -1,4 +1,5 @@ diff --git a/apps/web/src/oj/problem/components/EditorToolbar.vue b/apps/web/src/oj/problem/components/EditorToolbar.vue index ed7a330..d7294df 100644 --- a/apps/web/src/oj/problem/components/EditorToolbar.vue +++ b/apps/web/src/oj/problem/components/EditorToolbar.vue @@ -35,6 +35,10 @@ const { problem, codeLanguages, canDraw } = storeToRefs(problemStore) * Flowchart 的「语言」—— 有的题流程图交了几百次、代码个位数,入口却在下拉的第一项里。 */ const drawing = computed(() => codeStore.code.language === "Flowchart") +const MODES = [ + { value: "code", label: "写代码", short: "代码" }, + { value: "draw", label: "画流程图", short: "流程图" }, +] as const const mode = computed({ get: () => (drawing.value ? "draw" : "code"), set: (value: "draw" | "code") => { @@ -156,10 +160,26 @@ const changeLanguage = (v: LANGUAGE) => {
- - {{ narrow ? "代码" : "写代码" }} - {{ narrow ? "流程图" : "画流程图" }} - + +
+ +
{ flex: 1 1 0; } +.mode { + flex: none; + display: flex; + gap: 2px; + padding: 3px; + border-radius: 6px; + background-color: rgba(128, 128, 128, 0.12); +} + +.mode-item { + height: 28px; + padding: 0 14px; + border: 0; + border-radius: 4px; + background: transparent; + font: inherit; + color: v-bind("theme.textColor2"); + cursor: pointer; +} + +.mode-item.active { + background-color: v-bind("theme.cardColor"); + color: v-bind("theme.textColor1"); + font-weight: 600; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); +} + .more { width: 34px; padding: 0; diff --git a/apps/web/src/oj/problem/components/FlowchartResult.vue b/apps/web/src/oj/problem/components/FlowchartResult.vue index 53d632d..aa27033 100644 --- a/apps/web/src/oj/problem/components/FlowchartResult.vue +++ b/apps/web/src/oj/problem/components/FlowchartResult.vue @@ -1,4 +1,5 @@ @@ -271,18 +275,14 @@ function goCode() { padding-bottom: 16px; } -.block { - margin-bottom: 14px; -} - .empty { margin-top: 48px; } .note { font-size: 13px; - opacity: 0.75; - margin: 2px 0 0; + color: v-bind("theme.textColor3"); + margin: 0 0 10px; } .pad { @@ -291,98 +291,220 @@ function goCode() { .history { display: flex; + align-items: center; + flex-wrap: wrap; gap: 6px; - overflow-x: auto; - padding: 2px 2px 8px; + margin-bottom: 12px; + font-size: 13px; +} + +.history-lead { + color: v-bind("theme.textColor2"); +} + +.arrow { + color: v-bind("theme.textColor3"); } .attempt { - flex: none; - display: flex; - flex-direction: column; - align-items: center; - gap: 1px; - min-width: 58px; - padding: 6px 8px; - border-radius: 8px; - border: 1px solid rgba(128, 128, 128, 0.25); - background: transparent; - color: inherit; + height: 26px; + box-sizing: border-box; + padding: 0 9px; + border-radius: 13px; + border: 1px solid transparent; + background-color: rgba(128, 128, 128, 0.12); + color: v-bind("theme.textColor2"); font: inherit; font-size: 13px; + font-variant-numeric: tabular-nums; cursor: pointer; } -.attempt b { - font-size: 17px; - font-variant-numeric: tabular-nums; -} - -.attempt-no { - font-size: 11px; - opacity: 0.65; -} - -.attempt.success b { - color: #18a058; -} - -.attempt.warning b { - color: #f0a020; -} - -.attempt.error b { - color: #d03050; -} - .attempt.active { - border-color: #2080f0; - box-shadow: 0 0 0 1px #2080f0; + background-color: v-bind("theme.textColor2"); + color: v-bind("theme.cardColor"); + font-weight: 600; } -.score { - font-size: 22px; +.attempt.pass.active { + background-color: v-bind("theme.successColor"); + color: v-bind("passText"); +} + +.attempt.pending { + display: inline-flex; + align-items: center; + cursor: default; + background: transparent; + border: 1px dashed v-bind("theme.successColor"); + color: v-bind("theme.successColorPressed"); +} + +.grading { + display: flex; + align-items: center; + gap: 14px; + padding: 18px; + margin-bottom: 10px; + border-radius: 8px; + background-color: rgba(24, 160, 88, 0.07); + border: 1px solid rgba(24, 160, 88, 0.25); +} + +.grading-title { + font-size: 16px; font-weight: 700; } -.pass { - margin: 10px 0 4px; - padding: 8px 12px; +.grading-note { + margin-top: 2px; + font-size: 13px; + color: v-bind("theme.textColor2"); +} + +.failed { + padding: 12px 14px; border-radius: 6px; - background-color: rgba(24, 160, 88, 0.1); + background-color: rgba(240, 160, 32, 0.1); + border: 1px solid rgba(240, 160, 32, 0.35); +} + +.score-line { + display: flex; + align-items: baseline; + flex-wrap: wrap; + gap: 4px 8px; + margin-bottom: 14px; +} + +.score { + font-size: 34px; + font-weight: 700; + line-height: 1; + font-variant-numeric: tabular-nums; +} + +.score.success { + color: v-bind("theme.successColorPressed"); +} + +.score.warning { + color: v-bind("theme.warningColorPressed"); +} + +.score.error { + color: v-bind("theme.errorColorPressed"); +} + +.score-unit { + color: v-bind("theme.textColor2"); +} + +.grade { + align-self: center; + padding: 2px 8px; + border-radius: 4px; + font-size: 13px; + font-weight: 700; + color: #fff; + background-color: v-bind("theme.errorColor"); +} + +.grade.success { + background-color: v-bind("theme.successColor"); + color: v-bind("passText"); +} + +.grade.warning { + background-color: v-bind("theme.warningColor"); } .feedback { - margin: 12px 0; - line-height: 1.7; + flex: 1 1 200px; + align-self: center; + margin-left: 6px; + font-size: 14px; + line-height: 1.6; } -h4 { - margin: 14px 0 6px; +/* 四项得分:两列,每项一条进度 */ +.criteria { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px 20px; + margin-bottom: 14px; +} + +.criterion-head { + display: flex; + justify-content: space-between; font-size: 14px; } -.suggestions ol { - margin: 0; - padding-left: 1.4em; - line-height: 1.7; +.criterion-score { + font-variant-numeric: tabular-nums; + color: v-bind("theme.textColor2"); } -.criterion { - padding: 6px 0; +.bar { + height: 6px; + margin: 4px 0; + border-radius: 3px; + background-color: rgba(128, 128, 128, 0.14); + overflow: hidden; } -.criterion + .criterion { - border-top: 1px dashed rgba(128, 128, 128, 0.2); +.bar-fill { + height: 100%; + border-radius: 3px; + background-color: v-bind("theme.successColor"); +} + +.bar-fill.warning { + background-color: v-bind("theme.warningColor"); +} + +.bar-fill.error { + background-color: v-bind("theme.errorColor"); } .comment { - display: block; - margin-top: 2px; font-size: 13px; + color: v-bind("theme.textColor2"); } -.load { - margin-top: 14px; +.suggestions { + padding: 12px 14px; + margin-bottom: 14px; + border-radius: 6px; + background-color: rgba(128, 128, 128, 0.06); + border: 1px solid v-bind("theme.dividerColor"); +} + +.suggestions-title { + font-weight: 600; + margin-bottom: 6px; +} + +.suggestion { + display: flex; + align-items: baseline; + gap: 8px; + line-height: 1.7; +} + +.tag { + flex: none; + padding: 0 6px; + border-radius: 3px; + font-size: 12px; + background-color: rgba(240, 160, 32, 0.16); + color: v-bind("theme.warningColorPressed"); +} + +.actions { + display: flex; + align-items: center; + gap: 12px; } diff --git a/apps/web/src/oj/problem/components/MobileActionBar.vue b/apps/web/src/oj/problem/components/MobileActionBar.vue index 9b94041..2cceb08 100644 --- a/apps/web/src/oj/problem/components/MobileActionBar.vue +++ b/apps/web/src/oj/problem/components/MobileActionBar.vue @@ -31,6 +31,8 @@ const canRunSamples = computed( +import { useThemeVars } from "naive-ui" import { useCodeStore } from "oj/store/code" import { findChinesePunctuation } from "oj/problem/utils/chinesePunctuation" import { @@ -23,23 +24,28 @@ const props = defineProps<{ }>() const codeStore = useCodeStore() +const theme = useThemeVars() const explanation = computed(() => explainPythonCompileError(props.errInfo)) /** 判题机的临时目录名(`/judger/run/<32 位随机串>/`)对学生没有意义,只剩文件名 */ const rawError = computed(() => props.errInfo.replace(/\/judger\/run\/[^/"]+\//g, "")) -/** 出错那一行拆成三段,中间那段是 `^` 标的地方 */ -const sourceParts = computed(() => { +/** + * 报错只点名第一处。别的行也有中文标点的话,一句话带过(设计稿「语法没过」: + * 「第 4 行也有一个中文冒号,编辑器里都标出来了」)。原来这里还摆一段出错那行的代码摘录, + * 编辑器里已经标着红,设计稿里去掉了 + */ +const otherPunctuationLines = computed(() => { const ex = explanation.value - if (!ex?.sourceLine) return null - const { sourceLine: text, caret } = ex - if (!caret) return { before: text, marked: "", after: "" } - return { - before: text.slice(0, caret.from), - marked: text.slice(caret.from, caret.to), - after: text.slice(caret.to), + if (!ex?.punctuation) return [] + const code = codeStore.code.value + const lines = new Set() + for (const fix of findChinesePunctuation(code)) { + lines.add(code.slice(0, fix.from).split("\n").length) } + lines.delete(ex.line ?? -1) + return [...lines].sort((a, b) => a - b) }) // 数的是编辑器里**现在**的代码:学生可能已经自己改掉几处了 @@ -72,68 +78,63 @@ onUnmounted(clearErrorMark) diff --git a/apps/web/src/oj/problem/components/ResultPane.vue b/apps/web/src/oj/problem/components/ResultPane.vue index cef45ef..14feefd 100644 --- a/apps/web/src/oj/problem/components/ResultPane.vue +++ b/apps/web/src/oj/problem/components/ResultPane.vue @@ -83,6 +83,9 @@ const canTrial = computed(() => { > {{ segment.label }} + 自己编数据试试,这里不判对错
@@ -135,6 +138,13 @@ const canTrial = computed(() => { cursor: pointer; } +.segment-note { + margin-left: auto; + align-self: center; + font-size: 13px; + color: v-bind("theme.textColor3"); +} + .segment.active { background-color: v-bind("theme.textColor1"); color: v-bind("theme.cardColor"); diff --git a/apps/web/src/oj/problem/components/SampleRunResult.vue b/apps/web/src/oj/problem/components/SampleRunResult.vue index f542d0e..53c2948 100644 --- a/apps/web/src/oj/problem/components/SampleRunResult.vue +++ b/apps/web/src/oj/problem/components/SampleRunResult.vue @@ -1,18 +1,17 @@