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
@@ -7,6 +7,8 @@ import { isFlowchartPass, useFlowchartStore } from "oj/store/flowchart"
|
||||
import { useLessonStore } from "oj/store/lesson"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import type { ProblemSet, ProblemSetProblem } from "utils/types"
|
||||
import { useProblemPageContext } from "../composables/problemPageContext"
|
||||
import ProblemChips, { type ChipItem } from "./ProblemChips.vue"
|
||||
@@ -27,6 +29,20 @@ import ProblemChips, { type ChipItem } from "./ProblemChips.vue"
|
||||
const CHIPS_FIT = 5
|
||||
|
||||
const ctx = useProblemPageContext()
|
||||
const { isMobile } = useBreakpoints()
|
||||
const theme = useThemeVars()
|
||||
const isDark = useDark()
|
||||
|
||||
/**
|
||||
* 三种入口各一种底色(设计稿「上下文条与协作状态」):课堂浅绿、题单浅蓝、比赛浅黄。
|
||||
* 字色在暗色下换成浅一档的,深绿、深蓝、深黄放在暗底上看不清
|
||||
*/
|
||||
const tone = computed(() => ({
|
||||
lessonText: isDark.value ? theme.value.primaryColor : theme.value.primaryColorPressed,
|
||||
setText: isDark.value ? "#8fb4ee" : "#1d4f9c",
|
||||
setBar: isDark.value ? "#6f9be0" : "#2f6bc4",
|
||||
contestText: isDark.value ? "#f2c97d" : "#8a5200",
|
||||
}))
|
||||
const userStore = useUserStore()
|
||||
const { problem } = storeToRefs(useProblemStore())
|
||||
const currentId = computed(() => problem.value?._id ?? "")
|
||||
@@ -85,6 +101,7 @@ const lesson = computed(() => {
|
||||
chips,
|
||||
done: list.filter((item) => item.myStatus === "accepted").length,
|
||||
total: list.length,
|
||||
position: index + 1,
|
||||
previous: chips[index - 1]?.to ?? null,
|
||||
next: chips[index + 1]?.to ?? null,
|
||||
}
|
||||
@@ -211,115 +228,274 @@ onSolvedHere(() => {
|
||||
|
||||
<template>
|
||||
<!-- 课堂 -->
|
||||
<div v-if="lesson" class="context-bar">
|
||||
<span class="lead">
|
||||
这节课 <b>做完 {{ lesson.done }}/{{ lesson.total }}</b>
|
||||
</span>
|
||||
<ProblemChips :items="lesson.chips" :current="currentId" />
|
||||
<n-button-group v-if="lesson.total <= CHIPS_FIT" size="small" class="steps">
|
||||
<n-button :disabled="!lesson.previous" title="上一题" @click="$router.push(lesson.previous!)">
|
||||
‹
|
||||
</n-button>
|
||||
<n-button :disabled="!lesson.next" title="下一题" @click="$router.push(lesson.next!)">
|
||||
›
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
<div v-if="lesson" class="context-bar lesson">
|
||||
<span class="lead lesson-lead">这节课 做完 {{ lesson.done }}/{{ lesson.total }}</span>
|
||||
<!-- 手机上放不下一排题号:只说是第几道,箭头做大(设计稿「手机:题目 / 代码 / 结果」) -->
|
||||
<span v-if="isMobile" class="meta">这是第 {{ lesson.position }} 道</span>
|
||||
<ProblemChips v-else :items="lesson.chips" :current="currentId" variant="lesson" />
|
||||
<div class="spacer" />
|
||||
<template v-if="isMobile || lesson.total <= CHIPS_FIT">
|
||||
<component
|
||||
:is="lesson.previous ? 'router-link' : 'span'"
|
||||
:to="lesson.previous ?? undefined"
|
||||
class="step"
|
||||
:class="{ disabled: !lesson.previous, big: isMobile }"
|
||||
aria-label="上一题"
|
||||
>
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M15 6l-6 6 6 6" />
|
||||
</svg>
|
||||
</component>
|
||||
<component
|
||||
:is="lesson.next ? 'router-link' : 'span'"
|
||||
:to="lesson.next ?? undefined"
|
||||
class="step forward"
|
||||
:class="{ disabled: !lesson.next, big: isMobile }"
|
||||
aria-label="下一题"
|
||||
>
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M9 6l6 6-6 6" />
|
||||
</svg>
|
||||
</component>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 题单 -->
|
||||
<div v-else-if="setBar" class="context-bar">
|
||||
<router-link :to="setBar.home" class="back" :title="`回到题单:${setBar.title}`">
|
||||
<span aria-hidden="true">‹</span>
|
||||
<span class="name">{{ setBar.title }}</span>
|
||||
<div v-else-if="setBar" class="context-bar problemset">
|
||||
<router-link :to="setBar.home" class="back set-back" :title="`回到题单:${setBar.title}`">
|
||||
‹ {{ setBar.title }}
|
||||
</router-link>
|
||||
<n-progress
|
||||
class="set-progress"
|
||||
type="line"
|
||||
:percentage="setBar.percentage"
|
||||
:show-indicator="false"
|
||||
:height="6"
|
||||
status="success"
|
||||
/>
|
||||
<span class="lead">
|
||||
<b>做完 {{ setBar.done }}/{{ setBar.total }}</b>
|
||||
<div class="set-progress" aria-hidden="true">
|
||||
<div class="set-progress-fill" :style="{ width: `${setBar.percentage}%` }" />
|
||||
</div>
|
||||
<span class="meta">
|
||||
做完 {{ setBar.done }}/{{ setBar.total }}
|
||||
<template v-if="setBar.position"> · 第 {{ setBar.position }} 题</template>
|
||||
</span>
|
||||
<n-button-group size="small" class="steps">
|
||||
<n-button :disabled="!setBar.previous" title="上一题" @click="$router.push(setBar.previous!)">
|
||||
‹
|
||||
</n-button>
|
||||
<n-button :disabled="!setBar.next" title="下一题" @click="$router.push(setBar.next!)">
|
||||
›
|
||||
</n-button>
|
||||
</n-button-group>
|
||||
<div class="spacer" />
|
||||
<component
|
||||
:is="setBar.previous ? 'router-link' : 'span'"
|
||||
:to="setBar.previous ?? undefined"
|
||||
class="step"
|
||||
:class="{ disabled: !setBar.previous, big: isMobile }"
|
||||
aria-label="上一题"
|
||||
>
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M15 6l-6 6 6 6" />
|
||||
</svg>
|
||||
</component>
|
||||
<component
|
||||
:is="setBar.next ? 'router-link' : 'span'"
|
||||
:to="setBar.next ?? undefined"
|
||||
class="step forward"
|
||||
:class="{ disabled: !setBar.next, big: isMobile }"
|
||||
aria-label="下一题"
|
||||
>
|
||||
<svg
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M9 6l6 6-6 6" />
|
||||
</svg>
|
||||
</component>
|
||||
</div>
|
||||
|
||||
<!-- 比赛 -->
|
||||
<div v-else-if="contestBar" class="context-bar">
|
||||
<router-link :to="contestBar.home" class="back" :title="`回到比赛:${contestBar.title}`">
|
||||
<span aria-hidden="true">‹</span>
|
||||
<span class="name">{{ contestBar.title }}</span>
|
||||
</router-link>
|
||||
<ProblemChips :items="contestBar.chips" :current="currentId" />
|
||||
<span v-if="contestBar.remaining" class="lead remaining">
|
||||
还剩 <b>{{ contestBar.remaining }}</b>
|
||||
</span>
|
||||
<span v-else class="lead">已结束</span>
|
||||
<router-link :to="contestBar.rank" class="steps">
|
||||
<n-button size="small">排名</n-button>
|
||||
<div v-else-if="contestBar" class="context-bar contest">
|
||||
<router-link
|
||||
:to="contestBar.home"
|
||||
class="back contest-back"
|
||||
:title="`回到比赛:${contestBar.title}`"
|
||||
>
|
||||
‹ {{ contestBar.title }}
|
||||
</router-link>
|
||||
<ProblemChips :items="contestBar.chips" :current="currentId" variant="contest" />
|
||||
<div class="spacer" />
|
||||
<template v-if="contestBar.remaining">
|
||||
<span class="meta">还剩</span>
|
||||
<span class="countdown">{{ contestBar.remaining }}</span>
|
||||
</template>
|
||||
<span v-else class="meta">已结束</span>
|
||||
<router-link :to="contestBar.rank" class="contest-rank">排名</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.context-bar {
|
||||
height: 44px;
|
||||
flex: none;
|
||||
box-sizing: border-box;
|
||||
padding: 0 12px 0 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 2px;
|
||||
flex: none;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.lesson {
|
||||
background-color: rgba(24, 160, 88, 0.07);
|
||||
border-bottom-color: rgba(24, 160, 88, 0.18);
|
||||
}
|
||||
|
||||
.problemset {
|
||||
gap: 10px;
|
||||
background-color: rgba(47, 107, 196, 0.06);
|
||||
border-bottom-color: rgba(47, 107, 196, 0.16);
|
||||
}
|
||||
|
||||
.contest {
|
||||
gap: 8px;
|
||||
padding-right: 16px;
|
||||
background-color: rgba(208, 138, 28, 0.08);
|
||||
border-bottom-color: rgba(208, 138, 28, 0.2);
|
||||
}
|
||||
|
||||
.lead {
|
||||
flex: none;
|
||||
margin-right: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.remaining b {
|
||||
font-variant-numeric: tabular-nums;
|
||||
.lesson-lead {
|
||||
color: v-bind("tone.lessonText");
|
||||
}
|
||||
|
||||
.meta {
|
||||
flex: none;
|
||||
color: v-bind("theme.textColor2");
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
/* 名字不跟题号那段抢地方:题号多了本来就能横向滚,名字挤成两个字就认不出了 */
|
||||
flex: none;
|
||||
max-width: 160px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.back:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.set-back {
|
||||
color: v-bind("tone.setText");
|
||||
}
|
||||
|
||||
.contest-back {
|
||||
max-width: 160px;
|
||||
color: v-bind("tone.contestText");
|
||||
}
|
||||
|
||||
.set-progress {
|
||||
flex: 1 1 60px;
|
||||
min-width: 40px;
|
||||
flex: none;
|
||||
width: 110px;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background-color: rgba(47, 107, 196, 0.16);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.steps {
|
||||
.set-progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
background-color: v-bind("tone.setBar");
|
||||
}
|
||||
|
||||
.countdown {
|
||||
flex: none;
|
||||
margin-left: auto;
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: v-bind("tone.contestText");
|
||||
}
|
||||
|
||||
.contest-rank {
|
||||
flex: none;
|
||||
color: v-bind("tone.contestText");
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.contest-rank:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ‹ ›:不带边框的箭头,「下一题」那个上色 */
|
||||
.step {
|
||||
flex: none;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
color: v-bind("theme.textColor2");
|
||||
}
|
||||
|
||||
.step.big {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.step.forward {
|
||||
color: v-bind("tone.lessonText");
|
||||
}
|
||||
|
||||
.problemset .step.forward {
|
||||
color: v-bind("tone.setText");
|
||||
}
|
||||
|
||||
.step:not(.disabled):hover {
|
||||
background-color: rgba(128, 128, 128, 0.1);
|
||||
}
|
||||
|
||||
.step.disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user