From 1d9686b929e9b3caa9a717d425beaa77fb96aec7 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 26 Aug 2026 10:08:30 -0600 Subject: [PATCH] =?UTF-8?q?feat(=E5=90=8E=E5=8F=B0=E7=94=A8=E6=88=B7):=20?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=91=98=E5=AF=86=E7=A0=81=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=89=93=E7=A0=81=EF=BC=8C=E7=82=B9=E3=80=8C=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E3=80=8D=E6=89=8D=E5=B1=95=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后台用户列表把所有账号的明文密码直接铺在表格里。学生密码是有意保留的运营需求 (老师要能查),但管理员账号的密码跟着一起明文显示,投屏或者旁人路过就看到了。 现在学生管理员/教师管理员/超管三类账号的密码默认渲染成 •••••• 加一个「显示」 按钮,点一下该行展开成明文(仍然是 TextCopy,点击复制照旧)。普通用户不变。 展开状态按 user id 记在 list.vue 里,listUsers() 里清空,所以翻页、搜索、换筛选、 换排序之后一律回到打码;不做定时自动隐藏,也不记进 localStorage。 只改前端。rawPassword 照旧随列表下发——/users* 每条路由本来就是 requireSuperAdmin, 非超管连列表都拿不到,要防的是屏幕被看到,不是拿到响应的人。改成「按需取单个密码」 反而要多开一个下发明文的端点,把面扩大。 rawPassword 为空时不打码:给一个点了什么也不显示的按钮没意义,直接走原来的 TextCopy。 顺带把角色标签缩短(学生管理员→生管、教师管理员→师管)。getUserRole 只有同一张表的 用户名列在用,密码列变宽之后那一列需要让出横向空间。 实跑验证(本机 dev 栈 + 超管登录):管理员行打码、学生行明文照旧、点「显示」后展开 且仍是可复制的按钮、搜索触发重新拉列表后回到打码。vue-tsc 与 build 均通过。 Co-Authored-By: Claude Opus 5 --- .../src/admin/user/components/Password.vue | 46 +++++++++++++++++++ apps/web/src/admin/user/list.vue | 14 +++++- apps/web/src/utils/functions.ts | 6 +-- 3 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 apps/web/src/admin/user/components/Password.vue diff --git a/apps/web/src/admin/user/components/Password.vue b/apps/web/src/admin/user/components/Password.vue new file mode 100644 index 0000000..4d4f6d8 --- /dev/null +++ b/apps/web/src/admin/user/components/Password.vue @@ -0,0 +1,46 @@ + + + diff --git a/apps/web/src/admin/user/list.vue b/apps/web/src/admin/user/list.vue index e4bd0d7..36f39df 100644 --- a/apps/web/src/admin/user/list.vue +++ b/apps/web/src/admin/user/list.vue @@ -13,6 +13,7 @@ import { } from "../api" import Actions from "./components/Actions.vue" import Name from "./components/Name.vue" +import Password from "./components/Password.vue" import { PROBLEM_PERMISSION, USER_TYPE } from "utils/constants" import { useRouteQuery } from "@vueuse/router" import TextCopy from "shared/components/TextCopy.vue" @@ -49,6 +50,9 @@ const sortOptions = [ ] const [create, toggleCreate] = useToggle(false) const password = ref("") +// 已经点开的管理员密码,按 user id 记。listUsers() 里清空,所以翻页/搜索/换筛选之后 +// 一律回到打码状态 —— 不做定时自动隐藏,也不记进 localStorage。 +const revealedPasswords = ref(new Set()) const userIDs = ref([]) const rowKey = (row: User) => row.id @@ -65,8 +69,13 @@ const columns: DataTableColumn[] = [ { title: "密码", key: "raw_password", - width: 100, - render: (row) => h(TextCopy, () => row.rawPassword), + width: 150, + render: (row) => + h(Password, { + user: row, + revealed: revealedPasswords.value.has(row.id), + onReveal: (id: number) => revealedPasswords.value.add(id), + }), }, { title: "创建时间", @@ -131,6 +140,7 @@ async function listUsers() { ) total.value = res.total users.value = res.results + revealedPasswords.value.clear() } function chooseUsers(rowKeys: DataTableRowKey[]) { diff --git a/apps/web/src/utils/functions.ts b/apps/web/src/utils/functions.ts index 31e58fd..f0ea79e 100644 --- a/apps/web/src/utils/functions.ts +++ b/apps/web/src/utils/functions.ts @@ -141,7 +141,7 @@ export function debounce any>( export function getUserRole(role: User["adminType"]): { type: "default" | "info" | "warning" | "error" - label: "普通" | "学生管理员" | "教师管理员" | "超管" + label: "普通" | "生管" | "师管" | "超管" } { const roleMap = { [USER_TYPE.REGULAR_USER]: { @@ -150,11 +150,11 @@ export function getUserRole(role: User["adminType"]): { }, [USER_TYPE.STUDENT_ADMIN]: { type: "info" as const, - label: "学生管理员" as const, + label: "生管" as const, }, [USER_TYPE.TEACHER_ADMIN]: { type: "warning" as const, - label: "教师管理员" as const, + label: "师管" as const, }, [USER_TYPE.SUPER_ADMIN]: { type: "error" as const,