mirror of
https://github.com/snowykami/neo-blog.git
synced 2025-09-26 11:06:23 +00:00
feat: 更新用户显示名称格式化逻辑,优化头像获取方式,增强用户界面
This commit is contained in:
@ -30,7 +30,7 @@ import {
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
import { getGravatarFromUser } from "@/utils/common/gravatar"
|
||||
import { getFallbackAvatarFromUsername } from "@/utils/common/username"
|
||||
import { formatDisplayName, getFallbackAvatarFromUsername } from "@/utils/common/username"
|
||||
import { useAuth } from "@/contexts/auth-context"
|
||||
import { userLogout } from "@/api/user"
|
||||
import { toast } from "sonner"
|
||||
@ -61,7 +61,7 @@ export function NavUser() {
|
||||
<AvatarFallback className="rounded-full">{getFallbackAvatarFromUsername(user.nickname || user.username)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.nickname}({user.username})</span>
|
||||
<span className="truncate font-medium">{formatDisplayName(user)}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
@ -82,7 +82,7 @@ export function NavUser() {
|
||||
<AvatarFallback className="rounded-full">{getFallbackAvatarFromUsername(user.nickname || user.username)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.nickname}({user.username})</span>
|
||||
<span className="truncate font-medium">{formatDisplayName(user)}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar"
|
||||
import Link from "next/link"
|
||||
|
||||
export function SiteHeader({ title = "Title" }: { title?: string }) {
|
||||
return (
|
||||
@ -14,14 +15,14 @@ export function SiteHeader({ title = "Title" }: { title?: string }) {
|
||||
<h1 className="text-base font-medium">{title}</h1>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
||||
<a
|
||||
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
|
||||
<Link
|
||||
href="https://github.com/snowykami/neo-blog"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="dark:text-foreground"
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -15,7 +15,7 @@ import { useToLogin } from "@/hooks/use-route";
|
||||
import { CircleUser } from "lucide-react";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { getGravatarFromUser } from "@/utils/common/gravatar";
|
||||
import { getFallbackAvatarFromUsername } from "@/utils/common/username";
|
||||
import { formatDisplayName, getFallbackAvatarFromUsername } from "@/utils/common/username";
|
||||
import { useAuth } from "@/contexts/auth-context";
|
||||
|
||||
export function AvatarWithDropdownMenu() {
|
||||
@ -39,8 +39,17 @@ export function AvatarWithDropdownMenu() {
|
||||
</Avatar> : <CircleUser className="h-8 w-8" />}
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="start">
|
||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
||||
<DropdownMenuContent className="w-auto" align="start">
|
||||
<DropdownMenuLabel>
|
||||
{user && <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{formatDisplayName(user)}</span>
|
||||
<span className="text-muted-foreground truncate text-xs">
|
||||
{user.email}
|
||||
</span>
|
||||
</div>
|
||||
</div>}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuGroup>
|
||||
{user && <DropdownMenuItem asChild>
|
||||
<Link href={`/u/${user?.username}`}>Profile</Link>
|
||||
@ -51,7 +60,7 @@ export function AvatarWithDropdownMenu() {
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={user ? handleLogout : toLogin}>
|
||||
{user ? `Logout (${user.username})` : "Login"}
|
||||
{user ? "Logout" : "Login"}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
@ -1,12 +1,12 @@
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
nickname: string;
|
||||
avatarUrl: string;
|
||||
nickname?: string;
|
||||
avatarUrl?: string;
|
||||
email: string;
|
||||
gender: string;
|
||||
gender?: string;
|
||||
role: string;
|
||||
language: string;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
export enum Role {
|
||||
|
@ -1,5 +1,21 @@
|
||||
import { User } from "@/models/user";
|
||||
|
||||
/**
|
||||
* Returns a single-character fallback avatar derived from a username.
|
||||
*
|
||||
* Rules:
|
||||
* - If `username` is falsy (empty string, null, or undefined), returns `"N"`.
|
||||
* - If the first character is an ASCII letter, returns the uppercase form of that character.
|
||||
* - Otherwise returns the first character as-is.
|
||||
*
|
||||
* @param username - The username to derive the avatar character from.
|
||||
* @returns A single character to use as a fallback avatar.
|
||||
*
|
||||
* @example
|
||||
* getFallbackAvatarFromUsername("alice"); // "A"
|
||||
* getFallbackAvatarFromUsername("1user"); // "1"
|
||||
* getFallbackAvatarFromUsername(""); // "N"
|
||||
*/
|
||||
export function getFallbackAvatarFromUsername(username: string): string {
|
||||
if (!username) {
|
||||
return "N";
|
||||
@ -11,6 +27,24 @@ export function getFallbackAvatarFromUsername(username: string): string {
|
||||
return firstChar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first-character avatar for a User, preferring nickname over username.
|
||||
*
|
||||
* Behavior:
|
||||
* - If `user.nickname` is present, derives the character from the nickname.
|
||||
* - Else if `user.username` is present, derives the character from the username.
|
||||
* - If neither is present, returns `"N"`.
|
||||
*
|
||||
* This function delegates the actual character selection logic to `getFallbackAvatarFromUsername`.
|
||||
*
|
||||
* @param user - The user object to extract the character from.
|
||||
* @returns A single character derived from the user's nickname or username, or `"N"` if neither is present.
|
||||
*
|
||||
* @example
|
||||
* getFirstCharFromUser({ nickname: "Bob", username: "bob123" }); // "B"
|
||||
* getFirstCharFromUser({ username: "1user" }); // "1"
|
||||
* getFirstCharFromUser({}); // "N"
|
||||
*/
|
||||
export function getFirstCharFromUser(user: User): string {
|
||||
if (user.nickname) {
|
||||
return getFallbackAvatarFromUsername(user.nickname);
|
||||
@ -20,3 +54,20 @@ export function getFirstCharFromUser(user: User): string {
|
||||
}
|
||||
return "N";
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a user's display name.
|
||||
*
|
||||
* - If the user has a `nickname`, returns `${nickname}(${username})`.
|
||||
* - Otherwise returns the `username`.
|
||||
*
|
||||
* @param user - The user whose display name is to be formatted.
|
||||
* @returns The formatted display name. If `username` is missing, the returned value may be falsy.
|
||||
*
|
||||
* @example
|
||||
* formatDisplayName({ nickname: "Sam", username: "sam42" }); // "Sam(sam42)"
|
||||
* formatDisplayName({ username: "sam42" }); // "sam42"
|
||||
*/
|
||||
export function formatDisplayName(user: User) :string {
|
||||
return user?.nickname ? `${user.nickname}(${user.username})` : user.username;
|
||||
}
|
Reference in New Issue
Block a user