- 提交结果 / 运行例子 / 自己输入改成浅灰底的分段按钮;状态大字和测试点方块放大一档 - 哪里不对一律是黄色小节标题(例子 1 没对上、错在没给你看的测试点、第 2 行出错),说明用正文大小 - 答错的对比竖着排成灰 / 绿 / 红三格,和题面的例子一个样子;运行时错误把出错那一行放进红格 - 做对了去掉浅绿卡片,改成「接着做」一句话加写着下一题题号题名的大按钮 - 提交详情页的答错对比不变 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
472ec69328
commit
566599614a
12 files changed
+356
-181
No files matched your search
@@ -71,8 +71,8 @@ const label = computed(() => {
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { applyFixes, clearErrorMark, currentCode, showErrorMark, showPunctuation
|
||||
import { explainCCompileError } from 'oj/problem/utils/cError'
|
||||
import { explainPythonCompileError } from 'utils/pythonError'
|
||||
import { stripJudgePath } from 'utils/judge'
|
||||
import SectionHead from './SectionHead.vue'
|
||||
|
||||
/**
|
||||
* 编译错误的中文说明卡片:Python 的语法错误走 pythonError.ts,C / C++ 的 gcc 报错走
|
||||
@@ -80,17 +81,17 @@ onUnmounted(clearErrorMark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="explain-card">
|
||||
<!-- 设计稿「题目页观感」结果 X:黄色小节标题说第几行,正文大小的中文说明;那一行编辑器里已经标红了 -->
|
||||
<div class="explain-section">
|
||||
<template v-if="explanation">
|
||||
<p class="explain">
|
||||
<b v-if="explanation.line">第 {{ explanation.line }} 行:</b>{{ explanation.message }}
|
||||
</p>
|
||||
<SectionHead icon="problem" tone="amber">{{ explanation.line ? `第 ${explanation.line} 行` : '代码的格式写错了' }}</SectionHead>
|
||||
<p class="explain">{{ explanation.message }}</p>
|
||||
<p v-if="otherPunctuationLines.length" class="explain other">第 {{ otherPunctuationLines.join('、') }} 行也有中文标点,编辑器里都标出来了。</p>
|
||||
<div v-if="explanation.punctuation">
|
||||
<div v-if="explanation.punctuation" class="fix">
|
||||
<n-button v-if="punctuationFixCount" type="primary" @click="fixPunctuation"> 把中文标点都换成英文({{ punctuationFixCount }} 处) </n-button>
|
||||
<n-text v-else-if="fixedCount" type="success"> 换好了 {{ fixedCount }} 处,再提交一次试试。 </n-text>
|
||||
</div>
|
||||
<n-collapse>
|
||||
<n-collapse class="raw-collapse">
|
||||
<n-collapse-item title="原始报错(英文)" name="raw">
|
||||
<div class="raw">{{ rawError }}</div>
|
||||
</n-collapse-item>
|
||||
@@ -98,6 +99,7 @@ onUnmounted(clearErrorMark)
|
||||
</template>
|
||||
<!-- 翻译表没覆盖到的句式(生产数据里约 0.3%):照旧给原文 -->
|
||||
<template v-else>
|
||||
<SectionHead icon="problem" tone="amber">代码的格式写错了</SectionHead>
|
||||
<p class="explain">请仔细检查,看看代码的格式是不是写错了!</p>
|
||||
<div class="raw">{{ rawError }}</div>
|
||||
</template>
|
||||
@@ -105,20 +107,15 @@ onUnmounted(clearErrorMark)
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 和答错的说明同一种浅灰卡片(设计稿「语法没过」) */
|
||||
.explain-card {
|
||||
padding: 12px 14px;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(128, 128, 128, 0.06);
|
||||
border: 1px solid v-bind('theme.dividerColor');
|
||||
.explain-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.explain {
|
||||
margin: 0;
|
||||
line-height: 1.7;
|
||||
margin: 0 0 10px;
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.explain.other {
|
||||
@@ -126,6 +123,10 @@ onUnmounted(clearErrorMark)
|
||||
color: v-bind('theme.textColor2');
|
||||
}
|
||||
|
||||
.fix {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.raw {
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import SectionHead from './SectionHead.vue'
|
||||
import { useThemeVars } from 'naive-ui'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useCodeStore } from 'oj/store/code'
|
||||
@@ -29,7 +31,7 @@ function fillSample(input: string) {
|
||||
<template>
|
||||
<!-- 设计稿「自己输入:顶替原来的『自测』模式」;「这里不判对错」在分段那一行右边(ResultPane) -->
|
||||
<div class="custom">
|
||||
<span class="label">输入</span>
|
||||
<SectionHead icon="input">输入</SectionHead>
|
||||
<n-input
|
||||
v-model:value="customInput"
|
||||
type="textarea"
|
||||
@@ -38,15 +40,16 @@ function fillSample(input: string) {
|
||||
class="mono"
|
||||
/>
|
||||
<div class="actions">
|
||||
<n-button type="primary" ghost :loading="customRunning" :disabled="!codeStore.code.value.trim()" @click="submissionStore.runCustom()">
|
||||
<n-button type="primary" secondary :loading="customRunning" :disabled="!codeStore.code.value.trim()" @click="submissionStore.runCustom()">
|
||||
<template #icon><Icon icon="ph:play-fill" /></template>
|
||||
用这组数据运行
|
||||
</n-button>
|
||||
<n-button v-for="(sample, index) in problem?.samples ?? []" :key="index" text class="fill" @click="fillSample(sample.input)">
|
||||
<n-button v-for="(sample, index) in problem?.samples ?? []" :key="index" text type="primary" class="fill" @click="fillSample(sample.input)">
|
||||
填入例子 {{ index + 1 }}
|
||||
</n-button>
|
||||
</div>
|
||||
|
||||
<span class="label">输出</span>
|
||||
<SectionHead icon="output" class="output-head">输出</SectionHead>
|
||||
<TrialErrorNote
|
||||
v-if="customResult !== null && customResult !== SubmissionStatus.accepted"
|
||||
:result="customResult"
|
||||
@@ -68,13 +71,14 @@ function fillSample(input: string) {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
.output-head {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.mono :deep(textarea) {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@@ -85,13 +89,12 @@ function fillSample(input: string) {
|
||||
}
|
||||
|
||||
.fill {
|
||||
font-size: 13px;
|
||||
color: v-bind('theme.textColor2');
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.output {
|
||||
margin: 0;
|
||||
min-height: 88px;
|
||||
min-height: 60px;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 10px;
|
||||
border-radius: 6px;
|
||||
@@ -99,7 +102,9 @@ function fillSample(input: string) {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 14px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
border: 1px solid rgba(128, 128, 128, 0.14);
|
||||
}
|
||||
|
||||
.output.empty {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import SectionHead from './SectionHead.vue'
|
||||
import { useThemeVars } from 'naive-ui'
|
||||
import { useLessonStore } from 'shared/store/lesson'
|
||||
import ChallengeList from './ChallengeList.vue'
|
||||
@@ -44,16 +45,17 @@ const lessonProblemIds = computed(() => lessonStore.activity?.problems.map((item
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 设计稿「答案正确:下一题在结果页签里」:浅绿卡片,一个 44px 的大按钮 -->
|
||||
<div v-if="lesson" class="lesson-next">
|
||||
<!--
|
||||
设计稿「题目页观感」结果 X:「接着做」一节,一句话说还剩几道,一个 44px 的大按钮写下一题。
|
||||
原来是一张浅绿卡片:左边竖栏已经有这节课的进度,不用再框一块
|
||||
-->
|
||||
<section v-if="lesson" class="lesson-next">
|
||||
<SectionHead icon="next">接着做</SectionHead>
|
||||
<template v-if="lesson.next">
|
||||
<div class="text">
|
||||
<span class="headline">{{ label }}还剩 {{ lesson.remaining }} 道</span>
|
||||
<span class="detail">
|
||||
{{ lesson.next.problemDisplayId }}
|
||||
{{ lesson.next.myStatus === 'tried' ? '你做过,还没对' : '你还没做过' }}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text">
|
||||
{{ label }}还剩 {{ lesson.remaining }} 道,下一道 {{ lesson.next.problemDisplayId }}
|
||||
{{ lesson.next.myStatus === 'tried' ? '你做过,还没对' : '你还没做过' }}。
|
||||
</p>
|
||||
<router-link :to="`/problem/${lesson.next.problemDisplayId}`" class="next">
|
||||
<span class="next-label">下一题:{{ lesson.next.problemDisplayId }} {{ lesson.next.title }}</span>
|
||||
<svg
|
||||
@@ -72,15 +74,12 @@ const lessonProblemIds = computed(() => lessonStore.activity?.problems.map((item
|
||||
</router-link>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="text">
|
||||
<span class="headline">{{ label }}都做完了,一共 {{ lesson.total }} 道</span>
|
||||
<span v-if="!challenges" class="detail">想接着练,可以去专题里挑一个</span>
|
||||
</div>
|
||||
<p class="text">{{ label }}都做完了,一共 {{ lesson.total }} 道。<template v-if="!challenges">想接着练,可以去专题里挑一个。</template></p>
|
||||
<router-link v-if="!challenges" to="/problemset" class="next secondary">
|
||||
<span class="next-label">去专题继续练</span>
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
<!-- 做完了:先给这节课里还能挑战的,再给专题(设计稿 StkDone)。挑战一条都没有时只剩专题那句 -->
|
||||
<template v-if="lesson && !lesson.next && challenges">
|
||||
<ChallengeList :problem-ids="lessonProblemIds" :language="language" :refresh-key="refreshKey" />
|
||||
@@ -90,30 +89,15 @@ const lessonProblemIds = computed(() => lessonStore.activity?.problems.map((item
|
||||
|
||||
<style scoped>
|
||||
.lesson-next {
|
||||
margin-top: 12px;
|
||||
padding: 16px 18px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(24, 160, 88, 0.07);
|
||||
border: 1px solid rgba(24, 160, 88, 0.25);
|
||||
margin-top: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.headline {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.detail {
|
||||
font-size: 13px;
|
||||
color: v-bind('theme.textColor2');
|
||||
margin: 0 0 12px;
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.next {
|
||||
@@ -125,10 +109,10 @@ const lessonProblemIds = computed(() => lessonStore.activity?.problems.map((item
|
||||
gap: 8px;
|
||||
height: 44px;
|
||||
padding: 0 20px;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
background-color: v-bind('theme.primaryColor');
|
||||
color: v-bind('buttonText');
|
||||
font-size: 15px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -101,8 +101,8 @@ const color = computed(() => {
|
||||
.result-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin-bottom: 12px;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.line {
|
||||
@@ -114,8 +114,8 @@ const color = computed(() => {
|
||||
|
||||
.badge {
|
||||
flex: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -125,12 +125,14 @@ const color = computed(() => {
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-size: 22px;
|
||||
line-height: 1.3;
|
||||
font-weight: 700;
|
||||
color: v-bind('color.text');
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 15px;
|
||||
color: v-bind('theme.textColor3');
|
||||
}
|
||||
|
||||
|
||||
@@ -74,20 +74,22 @@ function pick(segment: Segment) {
|
||||
<template>
|
||||
<FlowchartResult v-if="drawing" />
|
||||
<div v-show="!drawing" class="result-pane">
|
||||
<!-- 设计稿:三个胶囊,选中的深色实心,带上那次结果的时间 -->
|
||||
<div v-if="showSegments" class="segments" role="tablist" aria-label="结果">
|
||||
<button
|
||||
v-for="segment in segments"
|
||||
:key="segment.value"
|
||||
type="button"
|
||||
role="tab"
|
||||
class="segment"
|
||||
:class="{ active: isActive(segment.value) }"
|
||||
:aria-selected="isActive(segment.value)"
|
||||
@click="pick(segment.value)"
|
||||
>
|
||||
{{ segment.label }}<template v-if="segment.time"> · {{ segment.time }}</template>
|
||||
</button>
|
||||
<!-- 三个分段,带上那次结果的时间 -->
|
||||
<div v-if="showSegments" class="segments">
|
||||
<div class="segment-track" role="tablist" aria-label="结果">
|
||||
<button
|
||||
v-for="segment in segments"
|
||||
:key="segment.value"
|
||||
type="button"
|
||||
role="tab"
|
||||
class="segment"
|
||||
:class="{ active: isActive(segment.value) }"
|
||||
:aria-selected="isActive(segment.value)"
|
||||
@click="pick(segment.value)"
|
||||
>
|
||||
{{ segment.label }}<span v-if="segment.time" class="segment-time">{{ segment.time }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<span v-if="resultSegment === 'custom'" class="segment-note">自己编数据试试,这里不判对错</span>
|
||||
</div>
|
||||
|
||||
@@ -116,35 +118,53 @@ function pick(segment: Segment) {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 设计稿「题目页观感」结果 X:浅灰底的分段按钮,选中的白底加粗(和工具栏「写代码 / 画流程图」一个样子) */
|
||||
.segments {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
align-items: center;
|
||||
gap: 8px 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.segment-track {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
padding: 3px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(128, 128, 128, 0.12);
|
||||
}
|
||||
|
||||
.segment {
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
border: 0;
|
||||
border-radius: 15px;
|
||||
background-color: rgba(128, 128, 128, 0.12);
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: v-bind('theme.textColor2');
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.segment-time {
|
||||
margin-left: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: v-bind('theme.textColor3');
|
||||
}
|
||||
|
||||
.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');
|
||||
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);
|
||||
}
|
||||
|
||||
.formatted {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { clearErrorMark, showErrorMark } from 'oj/problem/utils/errorMark'
|
||||
import { explainRuntimeError, type RuntimeErrorInfo } from 'oj/problem/utils/runtimeError'
|
||||
import SectionHead from './SectionHead.vue'
|
||||
|
||||
/**
|
||||
* 运行时错误的中文说明卡片(诊断由后端 judge/runtime-diagnosis.ts 给)。
|
||||
* 运行时错误的中文说明(诊断由后端 judge/runtime-diagnosis.ts 给)。
|
||||
* 知道是第几行的,就把那一行代码摆出来,并在编辑器里标红;卸掉时清掉标记。
|
||||
*/
|
||||
const props = defineProps<{
|
||||
@@ -34,34 +35,50 @@ onUnmounted(clearErrorMark)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-card embedded class="explain-card">
|
||||
<n-flex vertical :size="12">
|
||||
<div class="explain">
|
||||
<b v-if="info.line">第 {{ info.line }} 行运行时出错:</b>{{ message }}
|
||||
</div>
|
||||
<pre v-if="sourceLine" class="explain-code">{{ sourceLine }}</pre>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
<!-- 设计稿「题目页观感」结果 X:黄色小节标题说第几行,正文大小的中文说明,再把那一行代码摆出来 -->
|
||||
<div class="explain-section">
|
||||
<SectionHead icon="problem" tone="amber">{{ info.line ? `第 ${info.line} 行出错` : '程序跑到一半出错了' }}</SectionHead>
|
||||
<p class="explain">{{ message }}</p>
|
||||
<div v-if="sourceLine" class="explain-code">
|
||||
<span class="line-no">{{ info.line }}</span>
|
||||
<pre>{{ sourceLine }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* 结果弹窗不限宽,长句子不折行会把弹窗撑出屏幕 */
|
||||
.explain-card {
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.explain {
|
||||
margin: 0 0 10px;
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
/* 出错的那一行:红底,前面写行号,和编辑器里标红的那一行对得上 */
|
||||
.explain-code {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 5px 10px;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(208, 48, 80, 0.06);
|
||||
border: 1px solid rgba(208, 48, 80, 0.24);
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.line-no {
|
||||
flex: none;
|
||||
font-weight: 700;
|
||||
color: #d03050;
|
||||
}
|
||||
|
||||
.explain-code pre {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
font: inherit;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
background-color: rgba(208, 48, 80, 0.12);
|
||||
}
|
||||
</style>
|
||||
@@ -103,22 +103,14 @@ const current = computed(() => sampleRuns.value[selected.value] ?? null)
|
||||
</ResultHeader>
|
||||
|
||||
<template v-if="current">
|
||||
<!-- 对上了:三栏摆出来就行 -->
|
||||
<!-- 对上了:输入、正确输出、你的输出竖着摆出来就行,你的输出也是绿的 -->
|
||||
<div v-if="current.result === SubmissionStatus.accepted" class="passed-card">
|
||||
<div class="blocks">
|
||||
<div class="block">
|
||||
<div class="label">输入</div>
|
||||
<pre>{{ current.input }}</pre>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="label">正确输出</div>
|
||||
<pre>{{ current.expected }}</pre>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="label">你的输出</div>
|
||||
<pre>{{ current.output }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<span class="label">输入</span>
|
||||
<pre class="pane in">{{ current.input }}</pre>
|
||||
<span class="label">正确输出</span>
|
||||
<pre class="pane ok">{{ current.expected }}</pre>
|
||||
<span class="label">你的输出</span>
|
||||
<pre class="pane ok">{{ current.output }}</pre>
|
||||
</div>
|
||||
<!-- 跑完了、输出对不上:和判完之后同一套说明 -->
|
||||
<WrongAnswerExplain
|
||||
@@ -134,6 +126,7 @@ const current = computed(() => sampleRuns.value[selected.value] ?? null)
|
||||
:code="samplesCode?.value ?? ''"
|
||||
:language="samplesCode?.language ?? ''"
|
||||
:plain="inContest"
|
||||
section
|
||||
/>
|
||||
<!-- 没跑完:编译不过、运行出错、超时 -->
|
||||
<TrialErrorNote
|
||||
@@ -165,15 +158,16 @@ const current = computed(() => sampleRuns.value[selected.value] ?? null)
|
||||
}
|
||||
|
||||
.run {
|
||||
height: 26px;
|
||||
height: 28px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 10px;
|
||||
border-radius: 13px;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -189,56 +183,58 @@ const current = computed(() => sampleRuns.value[selected.value] ?? null)
|
||||
color: v-bind('theme.errorColorPressed');
|
||||
}
|
||||
|
||||
.run.active {
|
||||
font-weight: 600;
|
||||
border-width: 1.5px;
|
||||
}
|
||||
|
||||
/* 选中的那个涂实心:设计稿「运行例子」 */
|
||||
.run.ok.active {
|
||||
border-color: v-bind('theme.successColor');
|
||||
background-color: v-bind('theme.successColor');
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.run.bad.active {
|
||||
border-color: v-bind('theme.errorColor');
|
||||
background-color: v-bind('theme.errorColor');
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 对上了:和答错时同一张两列的网格(标签 | 格子),只是你的输出也是绿的 */
|
||||
.passed-card {
|
||||
padding: 12px 14px;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(128, 128, 128, 0.06);
|
||||
border: 1px solid v-bind('theme.dividerColor');
|
||||
}
|
||||
|
||||
.blocks {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.block {
|
||||
min-width: 0;
|
||||
padding: 6px 10px 8px;
|
||||
border-radius: 5px;
|
||||
background-color: v-bind('theme.cardColor');
|
||||
border: 1px solid v-bind('theme.dividerColor');
|
||||
grid-template-columns: 64px minmax(0, 1fr);
|
||||
gap: 6px 8px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
line-height: 36px;
|
||||
white-space: nowrap;
|
||||
color: v-bind('theme.textColor3');
|
||||
}
|
||||
|
||||
.block pre {
|
||||
.pane {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 5px 10px;
|
||||
border-radius: 6px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
font-family: Monaco, Consolas, monospace;
|
||||
font-size: 14px;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.pane.in {
|
||||
background-color: rgba(128, 128, 128, 0.07);
|
||||
border: 1px solid rgba(128, 128, 128, 0.14);
|
||||
}
|
||||
|
||||
.pane.ok {
|
||||
background-color: rgba(24, 160, 88, 0.08);
|
||||
border: 1px solid rgba(24, 160, 88, 0.26);
|
||||
}
|
||||
|
||||
.note {
|
||||
margin: 12px 0 0;
|
||||
margin: 16px 0 0;
|
||||
font-size: 13px;
|
||||
color: v-bind('theme.textColor3');
|
||||
}
|
||||
|
||||
@@ -5,9 +5,23 @@ import { Icon } from '@iconify/vue'
|
||||
* (「自己输入数据试试 ›」「放大看 ›」)。设计稿「题目页观感」⑦,2026-11-04 定。
|
||||
*
|
||||
* 语法要求、提示用黄色:一个是硬性条件、一个是帮忙的话,扫一眼要能和别的节分开。
|
||||
* 结果页签里「例子 1 没对上」「第 2 行出错」这类说哪里不对的也用黄色(设计稿结果 X)。
|
||||
* 明暗两套色都在 CSS 里(html.dark),别在这里调 useDark() —— 理由见 ProblemTypeTag。
|
||||
*/
|
||||
export type SectionIcon = 'description' | 'input' | 'output' | 'samples' | 'hint' | 'rules' | 'table' | 'expected' | 'flowchart' | 'similar'
|
||||
export type SectionIcon =
|
||||
| 'description'
|
||||
| 'input'
|
||||
| 'output'
|
||||
| 'samples'
|
||||
| 'hint'
|
||||
| 'rules'
|
||||
| 'table'
|
||||
| 'expected'
|
||||
| 'flowchart'
|
||||
| 'similar'
|
||||
| 'problem'
|
||||
| 'next'
|
||||
| 'challenge'
|
||||
|
||||
const ICON: Record<SectionIcon, string> = {
|
||||
description: 'ph:book-open',
|
||||
@@ -20,6 +34,10 @@ const ICON: Record<SectionIcon, string> = {
|
||||
expected: 'ph:check-circle',
|
||||
flowchart: 'ph:flow-arrow',
|
||||
similar: 'ph:shuffle-angular',
|
||||
// 结果页签:哪里不对、接着做、还能挑战
|
||||
problem: 'ph:warning-circle',
|
||||
next: 'ph:arrow-right',
|
||||
challenge: 'ph:target',
|
||||
}
|
||||
|
||||
const { icon, tone = 'green' } = defineProps<{
|
||||
|
||||
@@ -16,6 +16,7 @@ import RuntimeErrorExplain from './RuntimeErrorExplain.vue'
|
||||
import WrongAnswerExplain from './WrongAnswerExplain.vue'
|
||||
import LessonNext from './LessonNext.vue'
|
||||
import ResultHeader from './ResultHeader.vue'
|
||||
import SectionHead from './SectionHead.vue'
|
||||
import SimilarProblems from './SimilarProblems.vue'
|
||||
import ChallengeList from './ChallengeList.vue'
|
||||
import ChallengeOutcome from './ChallengeOutcome.vue'
|
||||
@@ -361,22 +362,30 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="!waitingReveal">
|
||||
<n-flex vertical v-if="compileError || runtimeError || sampleCheck || msg || infoTable.length || submission.statisticInfo?.ast_results?.length">
|
||||
<n-flex
|
||||
vertical
|
||||
:size="20"
|
||||
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>
|
||||
<WrongAnswerExplain v-if="sampleCheck" full section :check="sampleCheck" :code="submission.code" :language="submission.language" />
|
||||
<!-- 语法要求逐条对没对:和题面的「语法要求」同一个黄色小节标题 -->
|
||||
<section v-if="submission.statisticInfo?.ast_results?.length">
|
||||
<SectionHead icon="rules" tone="amber">语法要求</SectionHead>
|
||||
<ul class="ast-rules">
|
||||
<li v-for="(rule, i) in submission.statisticInfo.ast_results" :key="i">
|
||||
<Icon
|
||||
:icon="rule.passed ? 'ph:check-circle-fill' : 'ph:x-circle-fill'"
|
||||
:width="17"
|
||||
:color="rule.passed ? theme.successColor : theme.errorColor"
|
||||
/>
|
||||
<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-card>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<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>
|
||||
@@ -386,7 +395,10 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
问过之后,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="showAIHint && !hintTarget" type="primary" secondary :loading="hintLoading" @click="fetchHint(submission.id)">
|
||||
<template #icon><Icon icon="ph:sparkle-fill" /></template>
|
||||
让 AI 分析我的代码
|
||||
</n-button>
|
||||
<n-button v-if="customRunHint" text type="primary" @click="submissionStore.revealResult('custom')">
|
||||
{{ customRunHint }}
|
||||
</n-button>
|
||||
@@ -425,7 +437,24 @@ const columns: DataTableColumn<JudgeCaseResult>[] = [
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
margin-top: 12px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.ast-rules {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.ast-rules li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 手机上「让 AI 分析我的代码」占满一行、44px 高,好按 */
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useProblemStore } from 'oj/store/problem'
|
||||
import { markdownToText } from 'oj/problem/utils/plainText'
|
||||
import { JUDGE_STATUS, SubmissionStatus } from 'utils/constants'
|
||||
import { classifyDiff, explainDiff, firstDifferentLine, type SampleCheck } from 'utils/wrongAnswer'
|
||||
import SectionHead from './SectionHead.vue'
|
||||
|
||||
/**
|
||||
* 答案错误时的样例对比(数据见后端 judge/run.ts 的 checkSamples)。
|
||||
@@ -31,6 +32,12 @@ const props = defineProps<{
|
||||
* 提示句全是对学生说的第二人称,整句换掉人称就读得通
|
||||
*/
|
||||
peer?: boolean
|
||||
/**
|
||||
* 题目页结果页签的样子(设计稿「题目页观感」结果 X):黄色小节标题说哪里不对,说明用正文大小,
|
||||
* 输入 / 正确输出 / 你的输出竖着排成灰、绿、红三格,和题面的例子一个样子。
|
||||
* 不给的话是原来那张浅灰卡片(提交详情页还在用)
|
||||
*/
|
||||
section?: boolean
|
||||
}>()
|
||||
|
||||
const theme = useThemeVars()
|
||||
@@ -114,21 +121,30 @@ function lines(text: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="explain-card">
|
||||
<p v-if="check.passed" class="explain">题目里的例子都对了,没通过的是隐藏的测试点。想想特殊情况:最大的数、最小的数、0、负数,或者题目里专门提到的情况。</p>
|
||||
<template v-else>
|
||||
<p class="explain">
|
||||
<b>例子 {{ (check.index ?? 0) + 1 }} 没有通过{{ plain ? '。' : ':' }}</b
|
||||
><template v-if="!plain">{{ hint }}</template>
|
||||
</p>
|
||||
<div :class="section ? 'explain-section' : 'explain-card'">
|
||||
<template v-if="section">
|
||||
<SectionHead icon="problem" tone="amber">
|
||||
{{ check.passed ? '例子都对了,错在没给你看的测试点' : `例子 ${(check.index ?? 0) + 1} 没对上` }}
|
||||
</SectionHead>
|
||||
<p v-if="check.passed" class="explain">想想特殊情况:最大的数、最小的数、0、负数,或者题目里专门提到的情况。</p>
|
||||
<p v-else-if="!plain" class="explain">{{ hint }}</p>
|
||||
</template>
|
||||
<p v-else-if="check.passed" class="explain">
|
||||
题目里的例子都对了,没通过的是隐藏的测试点。想想特殊情况:最大的数、最小的数、0、负数,或者题目里专门提到的情况。
|
||||
</p>
|
||||
<p v-else class="explain">
|
||||
<b>例子 {{ (check.index ?? 0) + 1 }} 没有通过{{ plain ? '。' : ':' }}</b
|
||||
><template v-if="!plain">{{ hint }}</template>
|
||||
</p>
|
||||
<template v-if="!check.passed">
|
||||
<div class="blocks">
|
||||
<div class="block">
|
||||
<div class="block in">
|
||||
<div class="label">输入</div>
|
||||
<div class="text">
|
||||
<div v-for="(line, i) in (check.input ?? '').split('\n')" :key="i" class="line" v-text="line" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="block ok">
|
||||
<div class="label">正确输出</div>
|
||||
<div class="text">
|
||||
<template v-for="(line, i) in lines(check.expected ?? '')" :key="i">
|
||||
@@ -139,7 +155,7 @@ function lines(text: string) {
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="block bad">
|
||||
<div class="label">{{ peer ? '学生' : '你' }}的输出</div>
|
||||
<div v-if="check.output" class="text">
|
||||
<template v-for="(line, i) in lines(check.output)" :key="i">
|
||||
@@ -241,4 +257,83 @@ function lines(text: string) {
|
||||
color: v-bind('theme.textColor2');
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/*
|
||||
* 题目页结果页签(section):标签一列、格子一列,输入灰、正确输出绿、你的输出红。
|
||||
* .block 用 display: contents 把「标签 + 格子」直接摊进这张两列的网格
|
||||
*/
|
||||
.explain-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.explain-section .explain {
|
||||
margin: 0 0 10px;
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.explain-section .blocks {
|
||||
grid-template-columns: 64px minmax(0, 1fr);
|
||||
gap: 6px 8px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.explain-section .block {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.explain-section .label {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 36px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.explain-section .bad .label {
|
||||
font-weight: 600;
|
||||
color: v-bind('theme.errorColor');
|
||||
}
|
||||
|
||||
.explain-section .text {
|
||||
min-width: 0;
|
||||
padding: 5px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
max-height: 260px;
|
||||
}
|
||||
|
||||
.explain-section .text.empty {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.explain-section .in .text {
|
||||
background-color: rgba(128, 128, 128, 0.07);
|
||||
border: 1px solid rgba(128, 128, 128, 0.14);
|
||||
}
|
||||
|
||||
.explain-section .ok .text {
|
||||
background-color: rgba(24, 160, 88, 0.08);
|
||||
border: 1px solid rgba(24, 160, 88, 0.26);
|
||||
}
|
||||
|
||||
.explain-section .bad .text {
|
||||
background-color: rgba(208, 48, 80, 0.06);
|
||||
border: 1px solid rgba(208, 48, 80, 0.24);
|
||||
}
|
||||
|
||||
.explain-section .line {
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.explain-section .cut {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.explain-section .requirement {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: v-bind('theme.textColor3');
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user