refactor(web): Header 只管顶栏,全局的东西挂回 App
Header 一直兼着「全局挂载点」:课堂求助的提示、新求助 toast、求助列表和 教师端协作弹框都寄生在里面,只因为顶栏是全局的。它其实不是 —— /admin/* 走的是 admin.vue,没有 Header,老师一进后台这四个消费者全部 卸载:求助照收,提示、角标、弹框一个都不出现,正好是 collab.ts 里写的 「老师可能正在后台改题时收到求助」那个场景。 这些东西跟着连接走,而连接在 App.vue 按登录态开关,所以搬进 CollabHost 挂在同一层。Header 因此不必再是单根组件,default.vue 那个靠 class 传 居中样式的写法也换成外层 div,连带删掉「必须挂在根 n-flex 内部」那段 补丁注释。 顺带: - 圆环扩散的暗黑切换抽成 useDarkTransition - 求助的角标/toast/列表不再限桌面端 —— 老师缩窗口也得知道有人在等; 接单确实要在电脑上写代码,那道闸挪进 HelpRequestList - 站名改 text 按钮,能 tab 到、回车能按 - logout 的两步收进 userStore.signOut() - 清掉死代码:handleMenuSelect 只认一个不存在的 key、active 里 ["user","setting"] 永远不生效的排除、两个 show:false 的菜单项 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QqqZwxtXLo2GTqMi51C94D
This commit is contained in:
1 parent
cbe6955c26
commit
e62f41f6c7
8 files changed
+233
-179
No files matched your search
@@ -0,0 +1,59 @@
|
||||
import { useDark } from "@vueuse/core"
|
||||
|
||||
/**
|
||||
* 圆环从哪儿开始扩散。正常点击就用指针落点;键盘触发(Enter / 空格)时浏览器给的
|
||||
* clientX/clientY 是 0,照用会让圆环从屏幕左上角冒出来——那种情况退回按钮自己的中心。
|
||||
* `event.detail` 是点击次数,键盘触发时为 0,用它区分最省事。
|
||||
*/
|
||||
function revealOrigin(event: MouseEvent) {
|
||||
if (event.detail > 0) return { x: event.clientX, y: event.clientY }
|
||||
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()
|
||||
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }
|
||||
}
|
||||
|
||||
/**
|
||||
* 暗黑模式切换 + 圆环扩散过渡。
|
||||
*
|
||||
* 从 Header 里搬出来的:这套动画细节和「顶栏该放什么」没有关系,
|
||||
* 哪个页面想再放一个主题开关都能直接用。
|
||||
*/
|
||||
export function useDarkTransition() {
|
||||
const isDark = useDark()
|
||||
|
||||
function toggleDark(event: MouseEvent) {
|
||||
if (!document.startViewTransition) {
|
||||
// 机房那批 Chrome 低于 94,没有 View Transitions,直接切、不做动画。
|
||||
isDark.value = !isDark.value
|
||||
return
|
||||
}
|
||||
const { x, y } = revealOrigin(event)
|
||||
// 半径要取到**最远**那个角的距离。用 hypot(x, y) 只覆盖到左上角,
|
||||
// 点在偏左上时右下角会有一块旧画面等圆环扩过去,看着像是没刷新。
|
||||
const radius = Math.hypot(
|
||||
Math.max(x, window.innerWidth - x),
|
||||
Math.max(y, window.innerHeight - y),
|
||||
)
|
||||
document
|
||||
.startViewTransition(() => {
|
||||
isDark.value = !isDark.value
|
||||
})
|
||||
.ready.then(() => {
|
||||
document.documentElement.animate(
|
||||
{
|
||||
clipPath: [
|
||||
`circle(0px at ${x}px ${y}px)`,
|
||||
`circle(${radius}px at ${x}px ${y}px)`,
|
||||
],
|
||||
},
|
||||
{
|
||||
duration: 400,
|
||||
easing: "ease-in-out",
|
||||
pseudoElement: "::view-transition-new(root)",
|
||||
},
|
||||
)
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
return { isDark, toggleDark }
|
||||
}
|
||||
Reference in new issue
Block a user