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>
|
||||||
<span class="muted">没做 {{ problemStats[selectedIndex]!.none }} 人</span>
|
<span class="muted">没做 {{ problemStats[selectedIndex]!.none }} 人</span>
|
||||||
<div class="spacer"></div>
|
<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>
|
<template #icon><Icon icon="ph:x-bold" /></template>
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -700,8 +706,10 @@ function shortTitle(title: string, size: number) {
|
|||||||
<template v-for="student in students" :key="student.userId">
|
<template v-for="student in students" :key="student.userId">
|
||||||
<tr v-if="groupStarts.has(student.userId)" class="group-row">
|
<tr v-if="groupStarts.has(student.userId)" class="group-row">
|
||||||
<td :colspan="board.problems.length + 4">
|
<td :colspan="board.problems.length + 4">
|
||||||
|
<span class="group-label">
|
||||||
{{ GROUP_LABEL[groupOf(student)] }}
|
{{ GROUP_LABEL[groupOf(student)] }}
|
||||||
<span class="muted">{{ groupCount[groupOf(student)] }}</span>
|
<span class="muted">{{ groupCount[groupOf(student)] }}</span>
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr :class="{ 'leave-row': groupOf(student) === 'leave' }">
|
<tr :class="{ 'leave-row': groupOf(student) === 'leave' }">
|
||||||
@@ -1164,6 +1172,14 @@ a.who {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 题多了表格要横着滑,名字钉在左边,不然滑过去就不知道是谁的格子 */
|
||||||
|
.grid .name {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: v-bind("theme.cardColor");
|
||||||
|
}
|
||||||
|
|
||||||
.grid th.col {
|
.grid th.col {
|
||||||
width: 96px;
|
width: 96px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -1213,6 +1229,12 @@ a.who {
|
|||||||
background: v-bind("theme.actionColor");
|
background: v-bind("theme.actionColor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 和名字一样钉在左边:横着滑过去也看得见这一组是什么 */
|
||||||
|
.group-label {
|
||||||
|
position: sticky;
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.group-row .muted {
|
.group-row .muted {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -1314,4 +1336,57 @@ a.cell {
|
|||||||
.meta {
|
.meta {
|
||||||
font-size: var(--oj-fs-sec);
|
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>
|
</style>
|
||||||
@@ -14,6 +14,7 @@ import { onKeyStroke } from "@vueuse/core"
|
|||||||
import { useThemeVars } from "naive-ui"
|
import { useThemeVars } from "naive-ui"
|
||||||
import SubmissionView from "oj/submission/components/SubmissionView.vue"
|
import SubmissionView from "oj/submission/components/SubmissionView.vue"
|
||||||
import UserName from "shared/components/UserName.vue"
|
import UserName from "shared/components/UserName.vue"
|
||||||
|
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||||
import type { ClassBoardProblem } from "utils/types"
|
import type { ClassBoardProblem } from "utils/types"
|
||||||
import ProblemBrief from "./ProblemBrief.vue"
|
import ProblemBrief from "./ProblemBrief.vue"
|
||||||
|
|
||||||
@@ -24,6 +25,9 @@ import ProblemBrief from "./ProblemBrief.vue"
|
|||||||
*
|
*
|
||||||
* 「题目」按钮:演示错误时题目和代码要并排(用户要的),点了左边出题目,再点收起。
|
* 「题目」按钮:演示错误时题目和代码要并排(用户要的),点了左边出题目,再点收起。
|
||||||
* 平时收着,代码照样占满宽。开没开记在这台电脑上,讲下一个人、下一道题还是那样
|
* 平时收着,代码照样占满宽。开没开记在这台电脑上,讲下一个人、下一道题还是那样
|
||||||
|
*
|
||||||
|
* 手机上(老师拿手机看看板):顶上拆成三行 —— 题目和关闭、一排人(横着滑)、两个按钮;
|
||||||
|
* 没有键盘所以不写「← → 换人」;题目展开时叠在代码上面,不并排
|
||||||
*/
|
*/
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
problem: ClassBoardProblem | null
|
problem: ClassBoardProblem | null
|
||||||
@@ -33,6 +37,7 @@ const show = defineModel<boolean>("show", { required: true })
|
|||||||
const index = defineModel<number>("index", { required: true })
|
const index = defineModel<number>("index", { required: true })
|
||||||
|
|
||||||
const theme = useThemeVars()
|
const theme = useThemeVars()
|
||||||
|
const { isMobile } = useBreakpoints()
|
||||||
/**
|
/**
|
||||||
* 颜色走内联的 CSS 变量,不用 <style> 里的 v-bind():n-modal 把内容挂到 body 下面,
|
* 颜色走内联的 CSS 变量,不用 <style> 里的 v-bind():n-modal 把内容挂到 body 下面,
|
||||||
* v-bind 的变量挂在组件根上,传不过去(弹层底色整个透明)
|
* v-bind 的变量挂在组件根上,传不过去(弹层底色整个透明)
|
||||||
@@ -50,6 +55,16 @@ const vars = computed(() => ({
|
|||||||
const current = computed(() => props.people[index.value] ?? null)
|
const current = computed(() => props.people[index.value] ?? null)
|
||||||
const showProblem = useStorage("oj_explain_show_problem", false)
|
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) {
|
function step(delta: -1 | 1) {
|
||||||
const next = index.value + delta
|
const next = index.value + delta
|
||||||
if (next >= 0 && next < props.people.length) index.value = next
|
if (next >= 0 && next < props.people.length) index.value = next
|
||||||
@@ -68,6 +83,7 @@ onKeyStroke(["ArrowLeft", "ArrowRight"], (event) => {
|
|||||||
<!-- 无 preset 的 n-modal 直接子节点要是普通元素 -->
|
<!-- 无 preset 的 n-modal 直接子节点要是普通元素 -->
|
||||||
<div
|
<div
|
||||||
class="sheet"
|
class="sheet"
|
||||||
|
:class="{ narrow: isMobile }"
|
||||||
:style="vars"
|
:style="vars"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
:aria-label="problem ? `讲 ${problem.problemDisplayId} 的错误` : '讲错误'"
|
:aria-label="problem ? `讲 ${problem.problemDisplayId} 的错误` : '讲错误'"
|
||||||
@@ -77,7 +93,7 @@ onKeyStroke(["ArrowLeft", "ArrowRight"], (event) => {
|
|||||||
<b>{{ problem.problemDisplayId }}</b> {{ problem.title }}
|
<b>{{ problem.problemDisplayId }}</b> {{ problem.title }}
|
||||||
<template v-if="people.length > 1"> · 错着的</template>
|
<template v-if="people.length > 1"> · 错着的</template>
|
||||||
</span>
|
</span>
|
||||||
<div class="people">
|
<div ref="peopleRef" class="people">
|
||||||
<button
|
<button
|
||||||
v-for="(person, i) in people"
|
v-for="(person, i) in people"
|
||||||
:key="person.userId"
|
:key="person.userId"
|
||||||
@@ -90,10 +106,14 @@ onKeyStroke(["ArrowLeft", "ArrowRight"], (event) => {
|
|||||||
<span class="kind">{{ person.label }}</span>
|
<span class="kind">{{ person.label }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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="spacer"></div>
|
||||||
|
<div class="buttons">
|
||||||
<n-button
|
<n-button
|
||||||
v-if="problem"
|
v-if="problem"
|
||||||
|
:size="isMobile ? 'small' : 'medium'"
|
||||||
:type="showProblem ? 'primary' : 'default'"
|
:type="showProblem ? 'primary' : 'default'"
|
||||||
:secondary="showProblem"
|
:secondary="showProblem"
|
||||||
@click="showProblem = !showProblem"
|
@click="showProblem = !showProblem"
|
||||||
@@ -102,13 +122,15 @@ onKeyStroke(["ArrowLeft", "ArrowRight"], (event) => {
|
|||||||
</n-button>
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
v-if="current"
|
v-if="current"
|
||||||
|
:size="isMobile ? 'small' : 'medium'"
|
||||||
tag="a"
|
tag="a"
|
||||||
:href="'/submission/' + current.submissionId"
|
:href="'/submission/' + current.submissionId"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
在新页面打开
|
在新页面打开
|
||||||
</n-button>
|
</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>
|
<template #icon><Icon icon="ph:x-bold" /></template>
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -238,4 +260,70 @@ kbd {
|
|||||||
.main {
|
.main {
|
||||||
min-width: 0;
|
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>
|
</style>
|
||||||
Reference in new issue
Block a user