feat(课堂看板): 「请假隐藏」不会用——改成班级旁一个「请假」按钮勾名单,请假的人变灰排到最后、今天有效
Deploy / deploy (push) Canceled after 0s

原来要先打开开关、名字变成带 × 的标签再点 ×,× 像删除;标完人从页面上消失,
恢复只能全部恢复。现在:

- 班级下拉旁「请假」按钮,标了人之后按钮直接写「请假 2 人:陈言、黄依露 · 修改」
- 弹框只列今天还没交过的人(请假的人一定在里面,刚上课时就是全班),勾上点确定
- 请假的人在表格里变灰排到最后,不算进进度和「还没交过」
- 交了题或举手就算来了,自动取消标记
- 有效期从两小时改成今天(请假一般是一整天),仍然只记在这台电脑上

顺带修掉:被标过的学生后来交了题,会从表格里整个消失、也不算进进度。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-09 10:38:25 +08:00
1 parent af2ca31de5
commit 1ba8928d41
4 files changed
+331 -198

No files matched your search

@@ -1,73 +0,0 @@
import { onMounted, ref } from "vue"
/**
* 课堂看板的「请假隐藏」:暂时把某个学生从「今天还没交过」里藏起来。
*
* 老师是把看板投在屏幕上盯着看的,没交名单里总有那么几个是请假/转班/学号错了的,
* 一直挂在上面会盖住真正需要盯的人。隐藏是**带过期时间**的(两小时,够一节课),
* 不是永久删除 —— 下节课自动回来,免得有人被无声地漏掉。
*
* 存在 localStorage 而不是后端:这是「这台电脑上这位老师这节课不想看谁」,
* 换个人、换台机器都不该继承。
*
* 原来在提交统计、流程图统计两个弹框里;统计改成回头看的页面之后(2026-09),
* 课上盯人的事归看板,这个也挪了过来。
*
* @param storageKey localStorage 的键
*/
export function useHiddenStudents(storageKey: string) {
/** 隐藏时长:两小时,一节课的量级 */
const HIDE_DURATION = 2 * 60 * 60 * 1000
function load(): Record<string, number> {
try {
return JSON.parse(localStorage.getItem(storageKey) ?? "{}")
} catch {
return {}
}
}
const hiddenStudents = ref<Record<string, number>>(load())
/** 「请假隐藏」开关:打开后名字旁边才出现那个 × */
const hideMode = ref(false)
function save(data: Record<string, number>) {
localStorage.setItem(storageKey, JSON.stringify(data))
}
function hideStudent(username: string) {
hiddenStudents.value = {
...hiddenStudents.value,
[username]: Date.now() + HIDE_DURATION,
}
save(hiddenStudents.value)
}
/** 恢复这几个人。只恢复看板上这个班的,别把别的班藏着的人一起放出来 */
function unhide(usernames: string[]) {
const drop = new Set(usernames)
hiddenStudents.value = Object.fromEntries(
Object.entries(hiddenStudents.value).filter(([name]) => !drop.has(name)),
)
save(hiddenStudents.value)
}
function isHidden(username: string) {
const expiresAt = hiddenStudents.value[username]
return !!expiresAt && expiresAt > Date.now()
}
onMounted(() => {
// 把已经到期的清掉再落一次盘,否则这张表只增不减
const now = Date.now()
const cleaned = Object.fromEntries(
Object.entries(hiddenStudents.value).filter(([, expiresAt]) => expiresAt > now),
)
hiddenStudents.value = cleaned
save(cleaned)
})
// 不导出 hiddenStudents 本身:用的地方要的是「这个人该不该显示」,
// 把那张表递出去只会让判断逻辑又散回组件里
return { hideMode, hideStudent, unhide, isHidden }
}
@@ -0,0 +1,84 @@
import { onMounted, ref } from "vue"
import { parseTime } from "utils/functions"
/**
* 课堂看板的「请假」:老师手动标记今天不在的学生,看板的人数、进度、「还没交过」都不算他们。
*
* **今天有效**(按东八区的日子),第二天自动清掉 —— 请假一般是请一整天,原来按两小时算,
* 连上两节课的第二节中途人又冒出来,得重标一遍。
*
* 存在 localStorage 而不是后端:这是「这台电脑上这位老师今天不想算谁」,
* 换个人、换台机器都不该继承(2026-10 用户定的,不上服务器)。
*
* 原来叫「请假隐藏」(开关 → 名字变成带 × 的标签 → 点 ×),用户说不会用,2026-10 改成
* 看板上一个「请假」按钮勾名单。旧键 `oj_hidden_students` 存的是两小时的过期时刻,
* 口径不同,不迁移,直接删掉。
*
* @param storageKey localStorage 的键
*/
export function useLeaveStudents(storageKey: string) {
/** 用户名 → 标记的那一天(YYYY-MM-DD,东八区) */
type Marks = Record<string, string>
function today() {
return parseTime(new Date(), "YYYY-MM-DD")
}
function load(): Marks {
try {
return JSON.parse(localStorage.getItem(storageKey) ?? "{}")
} catch {
return {}
}
}
function save(data: Marks) {
try {
localStorage.setItem(storageKey, JSON.stringify(data))
} catch {
// 存不下就只在这次打开里有效,不影响看板
}
}
const marks = ref<Marks>(load())
function onLeave(username: string) {
return marks.value[username] === today()
}
/**
* 把 `usernames` 这批人的标记换成 `selected`:在里面但没选的取消,选了的标上。
* 只动这一批(看板上这个班),别的班标着的人不受影响
*/
function setLeave(usernames: string[], selected: string[]) {
const scope = new Set(usernames)
const day = today()
const next = Object.fromEntries(
Object.entries(marks.value).filter(([name]) => !scope.has(name)),
)
for (const name of selected) next[name] = day
marks.value = next
save(next)
}
function cancelLeave(usernames: string[]) {
setLeave(usernames, [])
}
onMounted(() => {
try {
localStorage.removeItem("oj_hidden_students")
} catch {
// 读写不了 localStorage 的环境,没什么可清的
}
// 把不是今天的清掉再落一次盘,否则这张表只增不减
const day = today()
const cleaned = Object.fromEntries(
Object.entries(marks.value).filter(([, marked]) => marked === day),
)
marks.value = cleaned
save(cleaned)
})
return { onLeave, setLeave, cancelLeave }
}