feat(web): 用户下拉菜单改为账号设置/退出,修复会话用户名空显示

This commit is contained in:
2026-10-07 19:54:51 +08:00
parent 129ce7e380
commit 716ff96b07
2 changed files with 79 additions and 15 deletions
+8
View File
@@ -51,6 +51,14 @@ const DUMMY_PASSWORD_HASH =
export function adminSessionRoutes() { export function adminSessionRoutes() {
const r = new OpenAPIHono<{ Variables: AppVars }>(); const r = new OpenAPIHono<{ Variables: AppVars }>();
// 只保护需要会话的端点:GET/DELETE /session、PUT /password
// POST /session 是登录、setup* 是引导——不能套鉴权
r.use("/session", async (c, next) => {
if (c.req.method === "POST") return next();
return requireAdminSession(c, next);
});
r.use("/password", requireAdminSession);
r.openapi( r.openapi(
createRoute({ createRoute({
method: "get", method: "get",
+71 -15
View File
@@ -5,11 +5,14 @@ import {
AccountBookOutlined, AccountBookOutlined,
AuditOutlined, AuditOutlined,
DashboardOutlined, DashboardOutlined,
DownOutlined,
KeyOutlined, KeyOutlined,
LogoutOutlined,
MenuFoldOutlined, MenuFoldOutlined,
MenuUnfoldOutlined, MenuUnfoldOutlined,
SettingOutlined, SettingOutlined,
TeamOutlined, TeamOutlined,
UserOutlined,
} from "@antdv-next/icons"; } from "@antdv-next/icons";
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
@@ -20,7 +23,12 @@ const { token } = theme.useToken();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const adminSession = useAdminSessionStore(); const adminSession = useAdminSessionStore();
const sessionQuery = useSession(); // 必须解构:未解构时经 reactive 访问会解包 Ref,再取 .value 会得到 undefined
const {
data: sessionData,
isPending: sessionPending,
isFetching: sessionFetching,
} = useSession();
const logout = useLogout(); const logout = useLogout();
const collapsed = ref(false); const collapsed = ref(false);
@@ -28,7 +36,7 @@ const collapsed = ref(false);
const brandTitle = "NAKU"; const brandTitle = "NAKU";
const brandDescription = "NewAPI 用量统计"; const brandDescription = "NewAPI 用量统计";
const username = computed( const username = computed(
() => adminSession.username || sessionQuery.data.value?.username || "", () => adminSession.username || sessionData.value?.username || "",
); );
/** 收起时仅剩图标——每项必须带 icon(交互共性侧边栏六项) */ /** 收起时仅剩图标——每项必须带 icon(交互共性侧边栏六项) */
@@ -53,9 +61,21 @@ function onMenuClick({ key }: MenuInfo) {
void router.push(String(key)); void router.push(String(key));
} }
async function onLogout() { const userMenuItems: MenuItemType[] = [
await logout.mutateAsync().catch(() => undefined); { key: "settings", icon: UserOutlined, label: "账号设置" },
void router.push({ name: "admin-login" }); { type: "divider", key: "d1" },
{ key: "logout", icon: LogoutOutlined, label: "退出", danger: true },
];
async function onUserMenuClick({ key }: MenuInfo) {
if (key === "settings") {
void router.push({ name: "admin-settings" });
return;
}
if (key === "logout") {
await logout.mutateAsync().catch(() => undefined);
void router.push({ name: "admin-login" });
}
} }
</script> </script>
@@ -96,12 +116,21 @@ async function onLogout() {
</a-layout-sider> </a-layout-sider>
<a-layout> <a-layout>
<a-layout-header class="admin-layout__header"> <a-layout-header class="admin-layout__header">
<div class="admin-layout__account"> <a-dropdown
<span class="admin-layout__username">{{ username || "…" }}</span> :menu="{ items: userMenuItems }"
<a-button size="small" :loading="logout.isPending.value" @click="onLogout"> placement="bottomRight"
登出 trigger="click"
</a-button> @menu-click="onUserMenuClick"
</div> >
<a class="admin-layout__account">
<UserOutlined class="admin-layout__account-icon" />
<span v-if="username" class="admin-layout__username">{{ username }}</span>
<span v-else class="admin-layout__username is-muted">
{{ sessionPending || sessionFetching ? "…" : "未登录" }}
</span>
<DownOutlined class="admin-layout__account-caret" />
</a>
</a-dropdown>
</a-layout-header> </a-layout-header>
<a-layout-content class="admin-layout__content"> <a-layout-content class="admin-layout__content">
<div <div
@@ -200,14 +229,41 @@ async function onLogout() {
} }
.admin-layout__account { .admin-layout__account {
display: flex; display: inline-flex;
align-items: center; align-items: center;
gap: 16px; gap: 8px;
line-height: normal; padding: 0 8px;
height: 40px;
color: rgba(0, 0, 0, 0.88);
cursor: pointer;
border-radius: 6px;
transition: background-color 0.2s;
}
.admin-layout__account:hover {
background: rgba(0, 0, 0, 0.06);
}
.admin-layout__account-icon {
font-size: 14px;
color: rgba(0, 0, 0, 0.65);
}
.admin-layout__account-caret {
font-size: 10px;
color: rgba(0, 0, 0, 0.45);
} }
.admin-layout__username { .admin-layout__username {
color: rgba(0, 0, 0, 0.65); color: rgba(0, 0, 0, 0.88);
max-width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-layout__username.is-muted {
color: rgba(0, 0, 0, 0.45);
} }
.admin-layout__content { .admin-layout__content {