fix(课堂看板): 手机上「讲错误」弹层挤成一团,人名和按钮叠在一起;表格横着滑就看不出是谁的格子
- 讲错误弹层在手机上拆成三行:题目和关闭、一排人(横着滑,换到的人自动滑进来)、两个按钮; 不写「← → 换人」;展开题目时叠在代码上面,不再左右并排 - 错法面板标题行:题名一行,人数挪到第二行,「人」字不再被挤到下一行 - 表格名字列和分组标题钉在左边,横着滑也知道是谁、哪一组 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
68fae03165
commit
c149b0a194
2 files changed
+167
-4
No files matched your search
@@ -615,7 +615,13 @@ function shortTitle(title: string, size: number) {
|
||||
</span>
|
||||
<span class="muted">没做 {{ problemStats[selectedIndex]!.none }} 人</span>
|
||||
<div class="spacer"></div>
|
||||
<n-button quaternary size="small" aria-label="收起" @click="selectedId = null">
|
||||
<n-button
|
||||
quaternary
|
||||
size="small"
|
||||
class="panel-close"
|
||||
aria-label="收起"
|
||||
@click="selectedId = null"
|
||||
>
|
||||
<template #icon><Icon icon="ph:x-bold" /></template>
|
||||
</n-button>
|
||||
</div>
|
||||
@@ -700,8 +706,10 @@ function shortTitle(title: string, size: number) {
|
||||
<template v-for="student in students" :key="student.userId">
|
||||
<tr v-if="groupStarts.has(student.userId)" class="group-row">
|
||||
<td :colspan="board.problems.length + 4">
|
||||
<span class="group-label">
|
||||
{{ GROUP_LABEL[groupOf(student)] }}
|
||||
<span class="muted">{{ groupCount[groupOf(student)] }}</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr :class="{ 'leave-row': groupOf(student) === 'leave' }">
|
||||
@@ -1164,6 +1172,14 @@ a.who {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* 题多了表格要横着滑,名字钉在左边,不然滑过去就不知道是谁的格子 */
|
||||
.grid .name {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
background: v-bind("theme.cardColor");
|
||||
}
|
||||
|
||||
.grid th.col {
|
||||
width: 96px;
|
||||
cursor: pointer;
|
||||
@@ -1213,6 +1229,12 @@ a.who {
|
||||
background: v-bind("theme.actionColor");
|
||||
}
|
||||
|
||||
/* 和名字一样钉在左边:横着滑过去也看得见这一组是什么 */
|
||||
.group-label {
|
||||
position: sticky;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
.group-row .muted {
|
||||
margin-left: 4px;
|
||||
font-weight: 400;
|
||||
@@ -1314,4 +1336,57 @@ a.cell {
|
||||
.meta {
|
||||
font-size: var(--oj-fs-sec);
|
||||
}
|
||||
|
||||
/* ---------- 手机 ---------- */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* 题名占一行(收起钉在右边),人数挪到第二行,别把「人」挤到下一行去 */
|
||||
.panel-head {
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 12px;
|
||||
}
|
||||
|
||||
.panel-head > span {
|
||||
order: 2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
flex: 1 1 calc(100% - 50px);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.panel-head .spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.panel-close {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.err-group {
|
||||
min-width: 0;
|
||||
flex: 1 1 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.start-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.start-input {
|
||||
flex-basis: 100%;
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.grid .name {
|
||||
width: auto;
|
||||
padding-left: 4px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.cell {
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -14,6 +14,7 @@ import { onKeyStroke } from "@vueuse/core"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import SubmissionView from "oj/submission/components/SubmissionView.vue"
|
||||
import UserName from "shared/components/UserName.vue"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import type { ClassBoardProblem } from "utils/types"
|
||||
import ProblemBrief from "./ProblemBrief.vue"
|
||||
|
||||
@@ -24,6 +25,9 @@ import ProblemBrief from "./ProblemBrief.vue"
|
||||
*
|
||||
* 「题目」按钮:演示错误时题目和代码要并排(用户要的),点了左边出题目,再点收起。
|
||||
* 平时收着,代码照样占满宽。开没开记在这台电脑上,讲下一个人、下一道题还是那样
|
||||
*
|
||||
* 手机上(老师拿手机看看板):顶上拆成三行 —— 题目和关闭、一排人(横着滑)、两个按钮;
|
||||
* 没有键盘所以不写「← → 换人」;题目展开时叠在代码上面,不并排
|
||||
*/
|
||||
const props = defineProps<{
|
||||
problem: ClassBoardProblem | null
|
||||
@@ -33,6 +37,7 @@ const show = defineModel<boolean>("show", { required: true })
|
||||
const index = defineModel<number>("index", { required: true })
|
||||
|
||||
const theme = useThemeVars()
|
||||
const { isMobile } = useBreakpoints()
|
||||
/**
|
||||
* 颜色走内联的 CSS 变量,不用 <style> 里的 v-bind():n-modal 把内容挂到 body 下面,
|
||||
* v-bind 的变量挂在组件根上,传不过去(弹层底色整个透明)
|
||||
@@ -50,6 +55,16 @@ const vars = computed(() => ({
|
||||
const current = computed(() => props.people[index.value] ?? null)
|
||||
const showProblem = useStorage("oj_explain_show_problem", false)
|
||||
|
||||
/** 一排人横着滑的时候,换到的人要滑进来(← → 换人、或者点开的就是排在后面的) */
|
||||
const peopleRef = ref<HTMLElement | null>(null)
|
||||
watch([index, show], async () => {
|
||||
if (!show.value) return
|
||||
await nextTick()
|
||||
peopleRef.value
|
||||
?.querySelector<HTMLElement>(".person.on")
|
||||
?.scrollIntoView({ block: "nearest", inline: "nearest" })
|
||||
})
|
||||
|
||||
function step(delta: -1 | 1) {
|
||||
const next = index.value + delta
|
||||
if (next >= 0 && next < props.people.length) index.value = next
|
||||
@@ -68,6 +83,7 @@ onKeyStroke(["ArrowLeft", "ArrowRight"], (event) => {
|
||||
<!-- 无 preset 的 n-modal 直接子节点要是普通元素 -->
|
||||
<div
|
||||
class="sheet"
|
||||
:class="{ narrow: isMobile }"
|
||||
:style="vars"
|
||||
role="dialog"
|
||||
:aria-label="problem ? `讲 ${problem.problemDisplayId} 的错误` : '讲错误'"
|
||||
@@ -77,7 +93,7 @@ onKeyStroke(["ArrowLeft", "ArrowRight"], (event) => {
|
||||
<b>{{ problem.problemDisplayId }}</b> {{ problem.title }}
|
||||
<template v-if="people.length > 1"> · 错着的</template>
|
||||
</span>
|
||||
<div class="people">
|
||||
<div ref="peopleRef" class="people">
|
||||
<button
|
||||
v-for="(person, i) in people"
|
||||
:key="person.userId"
|
||||
@@ -90,10 +106,14 @@ onKeyStroke(["ArrowLeft", "ArrowRight"], (event) => {
|
||||
<span class="kind">{{ person.label }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<span v-if="people.length > 1" class="keys"><kbd>←</kbd><kbd>→</kbd> 换人</span>
|
||||
<span v-if="people.length > 1 && !isMobile" class="keys"
|
||||
><kbd>←</kbd><kbd>→</kbd> 换人</span
|
||||
>
|
||||
<div class="spacer"></div>
|
||||
<div class="buttons">
|
||||
<n-button
|
||||
v-if="problem"
|
||||
:size="isMobile ? 'small' : 'medium'"
|
||||
:type="showProblem ? 'primary' : 'default'"
|
||||
:secondary="showProblem"
|
||||
@click="showProblem = !showProblem"
|
||||
@@ -102,13 +122,15 @@ onKeyStroke(["ArrowLeft", "ArrowRight"], (event) => {
|
||||
</n-button>
|
||||
<n-button
|
||||
v-if="current"
|
||||
:size="isMobile ? 'small' : 'medium'"
|
||||
tag="a"
|
||||
:href="'/submission/' + current.submissionId"
|
||||
target="_blank"
|
||||
>
|
||||
在新页面打开
|
||||
</n-button>
|
||||
<n-button quaternary aria-label="关闭" @click="show = false">
|
||||
</div>
|
||||
<n-button quaternary class="close" aria-label="关闭" @click="show = false">
|
||||
<template #icon><Icon icon="ph:x-bold" /></template>
|
||||
</n-button>
|
||||
</div>
|
||||
@@ -238,4 +260,70 @@ kbd {
|
||||
.main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* ---------- 手机 ---------- */
|
||||
|
||||
.sheet.narrow {
|
||||
width: calc(100vw - 16px);
|
||||
max-height: calc(100vh - 16px);
|
||||
padding: 12px 14px 16px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 第一行题目 + 关闭,第二行一排人(横着滑),第三行两个按钮 */
|
||||
.narrow .top {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.narrow .problem {
|
||||
grid-row: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.narrow .close {
|
||||
grid-row: 1;
|
||||
grid-column: 2;
|
||||
margin-right: -6px;
|
||||
}
|
||||
|
||||
.narrow .people {
|
||||
grid-column: 1 / -1;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.narrow .person {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.narrow .spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.narrow .buttons {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* 题目叠在代码上面,自己滚,别把代码顶出一屏 */
|
||||
.narrow .body.split {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.narrow .statement {
|
||||
position: static;
|
||||
max-height: 40vh;
|
||||
padding: 0 0 12px;
|
||||
border-bottom: 1px solid var(--divider);
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user