style(题目): 题目页照设计稿「机房(方案 B)」重排——顶满的两栏、带底色的上下文条、下划线页签、新的结果和题面样式
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:
xuyueandClaude Opus 5.5 committed 2026-09-28 20:04:20 +08:00
1 parent 83b6b7a81a
commit 1fd9bf67c2
17 files changed
+1800 -706

No files matched your search

@@ -5,8 +5,9 @@ import type { RouteLocationRaw } from "vue-router"
/**
* 上下文条里的一排题号(课堂、比赛共用)。只放题号,题目名在鼠标停上去时给。
*
* 状态用图标区分,不只靠颜色:✓ 做完(实心绿)/ ! 做过没对(橙)/ 灰底 没做;当前题描边。
* 一节课一般 5 道以内,一行放得下;多了(比赛、老师一次布置了一长串)就横向滚动,
* 状态用图标区分,不只靠颜色:✓ 做完(实心绿)/ 做过没对(课堂是橙色 !,比赛是红色 ✕)/
* 灰底 没做;当前题描边,做完了的当前题实心绿再套一圈。课堂是圆角胶囊、比赛是方角
* (设计稿「上下文条与协作状态」)。一节课一般 5 道以内,一行放得下;多了就横向滚动,
* 当前题保持在视野里。
*/
export interface ChipItem {
@@ -19,17 +20,22 @@ export interface ChipItem {
const props = defineProps<{
items: ChipItem[]
current: string
variant: "lesson" | "contest"
}>()
const theme = useThemeVars()
/**
* 「做完」的实心绿上用什么字色。暗色主题的成功色是浅薄荷绿,白字几乎看不见 ——
* 和 Naive 自己的主按钮一样,暗色下换深色字
*/
/** 「做完」的实心绿上用什么字色:暗色主题的成功色是浅薄荷绿,白字几乎看不见 */
const isDark = useDark()
const doneText = computed(() => (isDark.value ? "rgba(0, 0, 0, 0.85)" : "#fff"))
const STATUS_TEXT = { done: "做完了", tried: "做过,还没对", none: "还没做" } as const
const STATUS_TEXT = computed(
() =>
({
done: "做完了",
tried: props.variant === "contest" ? "交过,还没对" : "做过,还没对",
none: "还没做",
}) as const,
)
const isCurrent = (item: ChipItem) => item.displayId.toLowerCase() === props.current.toLowerCase()
@@ -49,7 +55,8 @@ watch(
</script>
<template>
<div ref="scroller" class="chips">
<!-- 类名带前缀:父组件(ContextBar)的 .lesson / .contest 会作用到子组件根元素上 -->
<div ref="scroller" class="chips" :class="`chips-${variant}`">
<n-tooltip v-for="item in items" :key="item.displayId" :delay="300">
<template #trigger>
<router-link
@@ -59,9 +66,48 @@ watch(
:aria-label="`${item.displayId} ${item.title}(${STATUS_TEXT[item.status]})`"
:aria-current="isCurrent(item) ? 'page' : undefined"
>
<!-- 用字符不用图标:图标是从 Iconify 源现拉的,拉不到时状态就只剩颜色 -->
<span v-if="item.status === 'done'" aria-hidden="true">✓</span>
<span v-else-if="item.status === 'tried'" aria-hidden="true">!</span>
<!-- 内嵌 SVG,不走 Iconify:图标源拉不到时状态就只剩颜色了 -->
<svg
v-if="item.status === 'done'"
width="11"
height="11"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M5 12l5 5 9-10" />
</svg>
<svg
v-else-if="item.status === 'tried' && variant === 'contest'"
width="11"
height="11"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
aria-hidden="true"
>
<path d="M6 6l12 12M18 6L6 18" />
</svg>
<svg
v-else-if="item.status === 'tried'"
width="11"
height="11"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="3"
stroke-linecap="round"
aria-hidden="true"
>
<path d="M12 7v6" />
<path d="M12 17h.01" />
</svg>
<span>{{ item.displayId }}</span>
</router-link>
</template>
@@ -78,9 +124,13 @@ watch(
flex: 0 1 auto;
min-width: 0;
overflow-x: auto;
scrollbar-width: thin;
/* 描边的外圈别被裁掉 */
padding: 3px 2px;
scrollbar-width: none;
/* 当前题外面那一圈别被裁掉 */
padding: 4px 3px;
}
.chips::-webkit-scrollbar {
display: none;
}
.chip {
@@ -89,14 +139,19 @@ watch(
align-items: center;
gap: 3px;
height: 26px;
padding: 0 9px;
box-sizing: border-box;
padding: 0 8px;
border-radius: 13px;
border: 1.5px solid transparent;
font-size: 13px;
font-variant-numeric: tabular-nums;
text-decoration: none;
color: v-bind("theme.textColor2");
background-color: rgba(128, 128, 128, 0.12);
border: 1px solid transparent;
background-color: rgba(128, 128, 128, 0.14);
}
.chips-contest .chip {
border-radius: 4px;
}
.chip.done {
@@ -105,23 +160,36 @@ watch(
}
.chip.tried {
color: v-bind("theme.warningColor");
color: v-bind("theme.warningColorPressed");
background-color: rgba(240, 160, 32, 0.12);
border-color: rgba(240, 160, 32, 0.5);
border: 1px solid rgba(240, 160, 32, 0.45);
}
.chip.current {
border-color: v-bind("theme.primaryColor");
box-shadow: 0 0 0 1px v-bind("theme.primaryColor");
font-weight: 600;
.chips-contest .chip.tried {
color: v-bind("theme.errorColorPressed");
background-color: rgba(208, 48, 80, 0.12);
border-color: transparent;
}
.chip.current:not(.done) {
color: v-bind("theme.primaryColorPressed");
background-color: v-bind("theme.cardColor");
border: 1.5px solid v-bind("theme.primaryColor");
font-weight: 700;
}
.chips-contest .chip.current:not(.done) {
color: v-bind("theme.warningColorPressed");
border-color: v-bind("theme.warningColor");
}
/* 当前题做完了:实心绿,外面再套一圈(中间隔一道底色) */
.chip.done.current {
border-color: v-bind("theme.successColor");
font-weight: 700;
border-color: v-bind("theme.primaryColorPressed");
box-shadow:
0 0 0 2px v-bind("theme.bodyColor"),
0 0 0 3px v-bind("theme.successColor");
0 0 0 2px v-bind("theme.cardColor"),
0 0 0 3.5px v-bind("theme.primaryColorPressed");
}
.chip:hover {