From 801d84c53b4c8aeb5e019e4fe6fd7b2249c3e12e Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Tue, 13 Oct 2026 21:48:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E7=99=BB=E5=BD=95):=20=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E6=A1=86=E6=94=B9=E6=88=90=E7=82=B9=E5=90=8D=E5=AD=97=EF=BC=9A?= =?UTF-8?q?=E6=89=93=E5=BC=80=E5=B0=B1=E6=98=AF=E8=BF=99=E4=B8=AA=E7=8F=AD?= =?UTF-8?q?=E7=9A=84=E5=90=8D=E5=8D=95=EF=BC=8C=E7=82=B9=E8=87=AA=E5=B7=B1?= =?UTF-8?q?=E5=86=8D=E8=BE=93=E5=AF=86=E7=A0=81=EF=BC=9B=E8=80=81=E5=B8=88?= =?UTF-8?q?=E5=92=8C=E5=BE=80=E5=B1=8A=E5=86=99=E7=94=A8=E6=88=B7=E5=90=8D?= =?UTF-8?q?=EF=BC=8C=E8=BF=99=E5=8F=B0=E7=94=B5=E8=84=91=E4=B8=8A=E6=AC=A1?= =?UTF-8?q?=E6=80=8E=E4=B9=88=E7=99=BB=E4=B8=8B=E6=AC=A1=E5=B0=B1=E6=80=8E?= =?UTF-8?q?=E4=B9=88=E5=BC=80=EF=BC=9B=E6=B3=A8=E5=86=8C=E5=AE=8C=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E7=99=BB=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 学生登录 / 管理员登录两个页签和「没有我所在的班级」合成一条「写用户名」,入口是名单底下的「名单里没有我」「老师登录」 - 名单按拼音排、可打拼音缩写筛,只剩一个人时回车就选中;这台电脑没登过时先列出全部班级 - 密码错就地标在密码框下面(「密码不对,再试一次」),错太多次按钮灰掉写清 15 分钟 - 注册保留邮箱,错在哪个框标在哪个框下面,按钮改「注册并登录」 - name / autocomplete 原来写在 n-input 上、落在外层 div,没到真正的 input;改走 input-props,点名字时的密码框才真的不会让浏览器填上一个人的密码 - 登法和班级一样跨退出登录保留;authModal store 只管开关,表单回到组件里 Co-Authored-By: Claude Opus 5.5 --- apps/web/CLAUDE.md | 2 +- apps/web/src/shared/components/Login.vue | 853 +++++++++++++++------- apps/web/src/shared/components/Signup.vue | 383 +++++++--- apps/web/src/shared/store/authModal.ts | 114 +-- apps/web/src/shared/store/user.ts | 11 +- apps/web/src/utils/constants.ts | 1 + 6 files changed, 859 insertions(+), 505 deletions(-) diff --git a/apps/web/CLAUDE.md b/apps/web/CLAUDE.md index 48bb69e..8db7a27 100644 --- a/apps/web/CLAUDE.md +++ b/apps/web/CLAUDE.md @@ -58,7 +58,7 @@ API 调用不按模块分:学生端全在 `oj/api.ts`、后台全在 `admin/ap 跨端的(登录、资料、标签、验证码)在 `shared/api.ts`。 Shared logic lives in `shared/`: -- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (login/signup form state), `collab` (help-request queue + collab room), `achievement` (解锁弹窗队列), `myFlowchart` (题面「你画的流程图」那张 A/S 图的 mermaid 源码) +- `store/` — Pinia stores: `user` (auth/roles), `config` (site-wide settings), `authModal` (登录框、注册框开没开), `collab` (help-request queue + collab room), `achievement` (解锁弹窗队列), `myFlowchart` (题面「你画的流程图」那张 A/S 图的 mermaid 源码) - `composables/` — `aiStream`, `pagination` (URL-synced), `websocket` (reconnect + heartbeat), `collabDoc` (Yjs binding for the collab channel), `configUpdate` (WS-pushed config sync), `useMermaid` (lazy Mermaid render), `darkTransition` (View Transitions,111 以下走降级分支), `leaveStudents` (课堂看板的「请假」), `problemJump` (按题号直达), `teacherHelp` (老师接单 / 打开学生编辑器), `chartTheme`, `breakpoints`, `rarity` - `layout/` — `default.vue` and `admin.vue` layout wrappers - `api.ts` — shared API calls (auth, profile, tags, captcha) diff --git a/apps/web/src/shared/components/Login.vue b/apps/web/src/shared/components/Login.vue index 97549df..03f5df5 100644 --- a/apps/web/src/shared/components/Login.vue +++ b/apps/web/src/shared/components/Login.vue @@ -1,34 +1,83 @@ diff --git a/apps/web/src/shared/components/Signup.vue b/apps/web/src/shared/components/Signup.vue index 750a30d..2a9e225 100644 --- a/apps/web/src/shared/components/Signup.vue +++ b/apps/web/src/shared/components/Signup.vue @@ -1,130 +1,287 @@ + + diff --git a/apps/web/src/shared/store/authModal.ts b/apps/web/src/shared/store/authModal.ts index 6016e2d..1332057 100644 --- a/apps/web/src/shared/store/authModal.ts +++ b/apps/web/src/shared/store/authModal.ts @@ -1,157 +1,47 @@ import { defineStore } from "pinia" /** - * 认证状态管理 Store - * 统一管理登录、注册相关的模态框状态和表单状态 + * 登录框、注册框开没开。表单本身在两个组件里:每次打开都从干净的表单开始 + * (机房一台电脑轮流坐人,别把上一个人的名字和密码留在框里),不需要跨组件共享。 */ export const useAuthModalStore = defineStore("authModal", () => { - // ==================== 模态框状态 ==================== const loginModalOpen = ref(false) const signupModalOpen = ref(false) - // ==================== 登录表单状态 ==================== - // class 为 null 表示还没选,别用空串 —— 空串是「没有我所在的班级」那一项的值。 - // username 同理用 null:空串在 n-select 里是「选了一个空值」,占位符不显示。 - const loginForm = reactive({ - class: null as string | null, - username: null as string | null, - password: "", - }) - - const loginLoading = ref(false) - const loginError = ref("") - - // ==================== 注册表单状态 ==================== - const signupForm = reactive({ - username: "", - email: "", - password: "", - passwordAgain: "", - }) - - const signupLoading = ref(false) - const signupError = ref("") - - // ==================== 模态框操作 ==================== - /** - * 打开登录模态框 - */ function openLoginModal() { loginModalOpen.value = true } - /** - * 关闭登录模态框 - */ function closeLoginModal() { loginModalOpen.value = false } - /** - * 打开注册模态框 - */ function openSignupModal() { signupModalOpen.value = true } - /** - * 关闭注册模态框 - */ function closeSignupModal() { signupModalOpen.value = false } - /** - * 从登录切换到注册 - */ function switchToSignup() { closeLoginModal() openSignupModal() } - /** - * 从注册切换到登录 - */ function switchToLogin() { closeSignupModal() openLoginModal() } - // ==================== 登录表单操作 ==================== - /** - * 设置登录加载状态 - */ - function setLoginLoading(loading: boolean) { - loginLoading.value = loading - } - - /** - * 设置登录错误信息 - */ - function setLoginError(error: string) { - loginError.value = error - } - - /** - * 清空登录错误 - */ - function clearLoginError() { - loginError.value = "" - } - - // ==================== 注册表单操作 ==================== - /** - * 设置注册加载状态 - */ - function setSignupLoading(loading: boolean) { - signupLoading.value = loading - } - - /** - * 设置注册错误信息 - */ - function setSignupError(error: string) { - signupError.value = error - } - - /** - * 清空注册错误 - */ - function clearSignupError() { - signupError.value = "" - } - return { - // 模态框状态 loginModalOpen, signupModalOpen, - - // 登录表单状态 - loginForm, - loginLoading, - loginError, - - // 注册表单状态 - signupForm, - signupLoading, - signupError, - - // 模态框操作 openLoginModal, closeLoginModal, openSignupModal, closeSignupModal, switchToSignup, switchToLogin, - - // 登录表单操作 - setLoginLoading, - setLoginError, - clearLoginError, - - // 注册表单操作 - setSignupLoading, - setSignupError, - clearSignupError, } }) diff --git a/apps/web/src/shared/store/user.ts b/apps/web/src/shared/store/user.ts index 04d96fc..f99d6d9 100644 --- a/apps/web/src/shared/store/user.ts +++ b/apps/web/src/shared/store/user.ts @@ -95,11 +95,14 @@ export const useUserStore = defineStore("user", () => { function clearProfile() { profile.value = null demoSwitch.value = false - // 登录框记的班级要跨退登活下来:机房一台机器对一个班,下课登出、下节课再来 - // 还是同一个班,清掉的话每个人都得重新选一遍。它是机器的属性,不是谁的隐私。 - const loginClass = storage.get(STORAGE_KEY.LOGIN_CLASS) + // 登录框记的班级和登法要跨退登活下来:机房一台机器对一个班,下课登出、下节课再来 + // 还是同一个班,清掉的话每个人都得重新选一遍;老师的电脑同理,下次还是写用户名。 + // 它们是机器的属性,不是谁的隐私。 + const keep = [STORAGE_KEY.LOGIN_CLASS, STORAGE_KEY.LOGIN_MODE].map( + (key) => [key, storage.get(key)] as const, + ) storage.clear() - if (loginClass !== null) storage.set(STORAGE_KEY.LOGIN_CLASS, loginClass) + for (const [key, value] of keep) if (value !== null) storage.set(key, value) } // 退登的两步(吊销服务端会话、清本地状态)绑在一起:只清本地会留一个还活着 diff --git a/apps/web/src/utils/constants.ts b/apps/web/src/utils/constants.ts index abd5a46..d951a86 100644 --- a/apps/web/src/utils/constants.ts +++ b/apps/web/src/utils/constants.ts @@ -157,6 +157,7 @@ export const STORAGE_KEY = { ADMIN_SIDER_COLLAPSED: "adminSiderCollapsed", DEMO_MODE: "demoMode", LOGIN_CLASS: "loginClass", + LOGIN_MODE: "loginMode", RANK_CLASS: "rankClass", }