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"
|
} from "@/components/ui/sidebar"
|
||||||
|
|
||||||
import { getGravatarFromUser } from "@/utils/common/gravatar"
|
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 { useAuth } from "@/contexts/auth-context"
|
||||||
import { userLogout } from "@/api/user"
|
import { userLogout } from "@/api/user"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@ -61,7 +61,7 @@ export function NavUser() {
|
|||||||
<AvatarFallback className="rounded-full">{getFallbackAvatarFromUsername(user.nickname || user.username)}</AvatarFallback>
|
<AvatarFallback className="rounded-full">{getFallbackAvatarFromUsername(user.nickname || user.username)}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<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">
|
<span className="text-muted-foreground truncate text-xs">
|
||||||
{user.email}
|
{user.email}
|
||||||
</span>
|
</span>
|
||||||
@ -82,7 +82,7 @@ export function NavUser() {
|
|||||||
<AvatarFallback className="rounded-full">{getFallbackAvatarFromUsername(user.nickname || user.username)}</AvatarFallback>
|
<AvatarFallback className="rounded-full">{getFallbackAvatarFromUsername(user.nickname || user.username)}</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<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">
|
<span className="text-muted-foreground truncate text-xs">
|
||||||
{user.email}
|
{user.email}
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { SidebarTrigger } from "@/components/ui/sidebar"
|
import { SidebarTrigger } from "@/components/ui/sidebar"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
export function SiteHeader({ title = "Title" }: { title?: string }) {
|
export function SiteHeader({ title = "Title" }: { title?: string }) {
|
||||||
return (
|
return (
|
||||||
@ -14,14 +15,14 @@ export function SiteHeader({ title = "Title" }: { title?: string }) {
|
|||||||
<h1 className="text-base font-medium">{title}</h1>
|
<h1 className="text-base font-medium">{title}</h1>
|
||||||
<div className="ml-auto flex items-center gap-2">
|
<div className="ml-auto flex items-center gap-2">
|
||||||
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
||||||
<a
|
<Link
|
||||||
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
|
href="https://github.com/snowykami/neo-blog"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="dark:text-foreground"
|
className="dark:text-foreground"
|
||||||
>
|
>
|
||||||
GitHub
|
GitHub
|
||||||
</a>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -15,11 +15,11 @@ import { useToLogin } from "@/hooks/use-route";
|
|||||||
import { CircleUser } from "lucide-react";
|
import { CircleUser } from "lucide-react";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { getGravatarFromUser } from "@/utils/common/gravatar";
|
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 { useAuth } from "@/contexts/auth-context";
|
||||||
|
|
||||||
export function AvatarWithDropdownMenu() {
|
export function AvatarWithDropdownMenu() {
|
||||||
const {user} = useAuth();
|
const { user } = useAuth();
|
||||||
const toLogin = useToLogin();
|
const toLogin = useToLogin();
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
@ -39,8 +39,17 @@ export function AvatarWithDropdownMenu() {
|
|||||||
</Avatar> : <CircleUser className="h-8 w-8" />}
|
</Avatar> : <CircleUser className="h-8 w-8" />}
|
||||||
</button>
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent className="w-56" align="start">
|
<DropdownMenuContent className="w-auto" align="start">
|
||||||
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
<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>
|
<DropdownMenuGroup>
|
||||||
{user && <DropdownMenuItem asChild>
|
{user && <DropdownMenuItem asChild>
|
||||||
<Link href={`/u/${user?.username}`}>Profile</Link>
|
<Link href={`/u/${user?.username}`}>Profile</Link>
|
||||||
@ -51,7 +60,7 @@ export function AvatarWithDropdownMenu() {
|
|||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem onClick={user ? handleLogout : toLogin}>
|
<DropdownMenuItem onClick={user ? handleLogout : toLogin}>
|
||||||
{user ? `Logout (${user.username})` : "Login"}
|
{user ? "Logout" : "Login"}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
nickname: string;
|
nickname?: string;
|
||||||
avatarUrl: string;
|
avatarUrl?: string;
|
||||||
email: string;
|
email: string;
|
||||||
gender: string;
|
gender?: string;
|
||||||
role: string;
|
role: string;
|
||||||
language: string;
|
language?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Role {
|
export enum Role {
|
||||||
|
@ -1,5 +1,21 @@
|
|||||||
import { User } from "@/models/user";
|
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 {
|
export function getFallbackAvatarFromUsername(username: string): string {
|
||||||
if (!username) {
|
if (!username) {
|
||||||
return "N";
|
return "N";
|
||||||
@ -11,6 +27,24 @@ export function getFallbackAvatarFromUsername(username: string): string {
|
|||||||
return firstChar;
|
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 {
|
export function getFirstCharFromUser(user: User): string {
|
||||||
if (user.nickname) {
|
if (user.nickname) {
|
||||||
return getFallbackAvatarFromUsername(user.nickname);
|
return getFallbackAvatarFromUsername(user.nickname);
|
||||||
@ -19,4 +53,21 @@ export function getFirstCharFromUser(user: User): string {
|
|||||||
return getFallbackAvatarFromUsername(user.username);
|
return getFallbackAvatarFromUsername(user.username);
|
||||||
}
|
}
|
||||||
return "N";
|
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