feat(举手与协作): 顶栏「N 人举手」点开按题分组带错法;看板多一列「在写」点了就能看他的代码;帮完了回看板、还有人就「下一个」
学生那边按钮写「xuyue 在帮你 / 在看你的代码」。顺带修了老师接单后编辑器里留着自己的 草稿或模板、学生的代码插在前面、两边从此不一致的问题。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
573a0f967f
commit
1fcd12f6f4
13 files changed
+621
-178
No files matched your search
@@ -6,6 +6,7 @@ import { useTone } from "oj/submission/composables/tone"
|
||||
import { groupClassOptions } from "oj/submission/utils"
|
||||
import UserName from "shared/components/UserName.vue"
|
||||
import { useLeaveStudents } from "shared/composables/leaveStudents"
|
||||
import { useTeacherHelp } from "shared/composables/teacherHelp"
|
||||
import { useCollabStore } from "shared/store/collab"
|
||||
import { useConfigStore } from "shared/store/config"
|
||||
import type { LessonLanguage } from "@oj2/contract"
|
||||
@@ -40,9 +41,18 @@ const message = useMessage()
|
||||
const theme = useThemeVars()
|
||||
const tone = useTone()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const { help, peek } = useTeacherHelp()
|
||||
|
||||
const board = ref<ClassBoard | null>(null)
|
||||
const className = ref<string | null>(null)
|
||||
/**
|
||||
* 带着 ?className= 进来的(协作条「帮完了」回来、举手面板「讲这道题」)就看那个班,
|
||||
* 否则第一次由后端猜
|
||||
*/
|
||||
const queryText = (value: unknown) => (typeof value === "string" && value ? value : null)
|
||||
const className = ref<string | null>(queryText(route.query.className))
|
||||
/** ?problem= 带来的题号:加载完选中那道题,下面展开它的错法(「讲这道题」) */
|
||||
let wantedProblem = queryText(route.query.problem)
|
||||
|
||||
const classOptions = computed(() => {
|
||||
const list = configStore.config?.classList ?? []
|
||||
@@ -56,6 +66,12 @@ async function load() {
|
||||
board.value = data
|
||||
// 第一次由后端猜,之后就固定在这个班上,别每次刷新都换
|
||||
className.value = data.className
|
||||
if (wantedProblem) {
|
||||
const wanted = wantedProblem.toLowerCase()
|
||||
const hit = data.problems.find((item) => item.problemDisplayId.toLowerCase() === wanted)
|
||||
if (hit) selectedId.value = hit.problemId
|
||||
wantedProblem = null
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(errorMessage(error))
|
||||
}
|
||||
@@ -69,6 +85,26 @@ function changeClass(value: string) {
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
// 已经停在看板上时又从举手面板点了「讲这道题」:同一条路由只换 query,组件不重建
|
||||
watch(
|
||||
() => route.query,
|
||||
(query) => {
|
||||
wantedProblem = queryText(query.problem)
|
||||
const next = queryText(query.className)
|
||||
if (next && next !== className.value) {
|
||||
className.value = next
|
||||
editing.value = false
|
||||
}
|
||||
load()
|
||||
},
|
||||
)
|
||||
|
||||
/** 举手多久了,每秒走一格 */
|
||||
const now = useNow({ scheduler: (cb) => useIntervalFn(cb, 1000) })
|
||||
function waitedText(createdAt: number) {
|
||||
const seconds = Math.max(0, Math.floor((now.value.getTime() - createdAt) / 1000))
|
||||
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`
|
||||
}
|
||||
const { pause } = useIntervalFn(load, REFRESH_MS)
|
||||
onUnmounted(pause)
|
||||
|
||||
@@ -637,6 +673,7 @@ function shortTitle(title: string, size: number) {
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="name">学生</th>
|
||||
<th class="narrow-col where-col">在写</th>
|
||||
<th
|
||||
v-for="(stat, i) in problemStats"
|
||||
:key="stat.problem.problemId"
|
||||
@@ -662,7 +699,7 @@ function shortTitle(title: string, size: number) {
|
||||
<tbody>
|
||||
<template v-for="student in students" :key="student.userId">
|
||||
<tr v-if="groupStarts.has(student.userId)" class="group-row">
|
||||
<td :colspan="board.problems.length + 3">
|
||||
<td :colspan="board.problems.length + 4">
|
||||
{{ GROUP_LABEL[groupOf(student)] }}
|
||||
<span class="muted">{{ groupCount[groupOf(student)] }}</span>
|
||||
</td>
|
||||
@@ -679,16 +716,34 @@ function shortTitle(title: string, size: number) {
|
||||
<UserName :username="student.username" />
|
||||
</a>
|
||||
<UserName v-else :username="student.username" />
|
||||
<n-tag
|
||||
v-if="helpOf(student)"
|
||||
size="small"
|
||||
:bordered="false"
|
||||
type="warning"
|
||||
class="help-tag"
|
||||
@click="collabStore.helpPanelOpen = true"
|
||||
<span v-if="helpOf(student)?.status === 'pending'" class="waited">
|
||||
举手 {{ waitedText(helpOf(student)!.createdAt) }}
|
||||
</span>
|
||||
</td>
|
||||
<!-- 在写:举手的「帮他」;别人在帮的写谁在帮;开着题目的点题号直接打开他正在写的
|
||||
代码(他没举手也行,他那边会显示「xxx 在看你的代码」) -->
|
||||
<td class="where-col">
|
||||
<n-button
|
||||
v-if="helpOf(student)?.status === 'pending'"
|
||||
type="primary"
|
||||
size="tiny"
|
||||
@click="help(student.userId)"
|
||||
>
|
||||
{{ helpOf(student)!.status === "active" ? "老师在帮" : "举手了" }}
|
||||
</n-tag>
|
||||
帮他 · {{ helpOf(student)!.problemId }}
|
||||
</n-button>
|
||||
<span v-else-if="helpOf(student)" class="helping">
|
||||
{{ helpOf(student)!.teacherName }} 在帮
|
||||
</span>
|
||||
<button
|
||||
v-else-if="student.editing"
|
||||
type="button"
|
||||
class="where"
|
||||
:title="`打开 ${student.username} 正在写的 ${student.editing}`"
|
||||
@click="peek(student.userId)"
|
||||
>
|
||||
{{ student.editing }}
|
||||
</button>
|
||||
<span v-else class="muted">—</span>
|
||||
</td>
|
||||
<td
|
||||
v-for="(cell, i) in student.cells"
|
||||
@@ -1202,11 +1257,50 @@ a.cell {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.help-tag {
|
||||
.waited {
|
||||
margin-left: 8px;
|
||||
font-size: 13px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: v-bind("tone('error').color");
|
||||
}
|
||||
|
||||
.grid .where-col {
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.where {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
font: inherit;
|
||||
font-size: 14px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: v-bind("tone('success').color");
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.where::before {
|
||||
content: "";
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
.where:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.helping {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: v-bind("tone('info').color");
|
||||
}
|
||||
|
||||
.leave-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
|
||||
Reference in new issue
Block a user