Skip to content

Commit baa7508

Browse files
committed
refactor: extract UserAvatar component and add default avatar
- Create unified UserAvatar component to handle default avatar logic - Replace all Avatar usages with UserAvatar component - Add DefaultAvatar.svg as default user avatar - Simplify avatar rendering across all pages
1 parent ed66a90 commit baa7508

6 files changed

Lines changed: 67 additions & 73 deletions

File tree

web/public/DefaultAvatar.svg

Lines changed: 11 additions & 0 deletions
Loading
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
2+
import type { ComponentProps } from 'react';
3+
4+
interface UserAvatarProps extends ComponentProps<typeof Avatar> {
5+
avatar?: string | null;
6+
fallbackClassName?: string;
7+
}
8+
9+
/**
10+
* 统一的用户头像组件
11+
* 自动处理默认头像显示逻辑
12+
*/
13+
function UserAvatar({ avatar, fallbackClassName, className, ...props }: UserAvatarProps) {
14+
return (
15+
<Avatar className={className} {...props}>
16+
{avatar ? (
17+
<AvatarImage src={avatar} />
18+
) : (
19+
<AvatarFallback className={fallbackClassName}>
20+
<img src="/DefaultAvatar.svg" alt="" className="size-full object-cover" />
21+
</AvatarFallback>
22+
)}
23+
</Avatar>
24+
);
25+
}
26+
27+
export default UserAvatar;

web/src/components/rote/roteItem.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
LinkIcon,
1010
PinIcon,
1111
SmilePlus,
12-
User,
1312
} from 'lucide-react';
1413
import moment from 'moment';
1514
import { memo, useCallback, useState } from 'react';
@@ -19,11 +18,11 @@ import { toast } from 'sonner';
1918

2019
import RoteEditor from '@/components/editor/RoteEditor';
2120
import { SoftBottom } from '@/components/others/SoftBottom';
21+
import UserAvatar from '@/components/others/UserAvatar';
2222
import AttachmentsGrid from '@/components/rote/AttachmentsGrid';
2323
import NoticeCreateBoard from '@/components/rote/NoticeCreateBoard';
2424
import { ReactionsPart } from '@/components/rote/Reactions';
2525
import RoteActionsMenu from '@/components/rote/RoteActionsMenu';
26-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
2726
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
2827
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
2928

@@ -73,15 +72,7 @@ function RoteItem({
7372
>
7473
{showAvatar && (
7574
<Link className="text-primary hidden shrink-0 xl:block" to={`/${rote.author.username}`}>
76-
<Avatar className="size-[40px] bg-[#00000010]">
77-
{rote.author.avatar ? (
78-
<AvatarImage src={rote.author.avatar} />
79-
) : (
80-
<AvatarFallback>
81-
<User className="size-4 text-[#00000030]" />
82-
</AvatarFallback>
83-
)}
84-
</Avatar>
75+
<UserAvatar avatar={rote.author.avatar} className="size-[40px] bg-[#00000010]" />
8576
</Link>
8677
)}
8778

web/src/pages/profile/index.tsx

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import defaultCover from '@/assets/img/defaultCover.png';
22
import NavBar from '@/components/layout/navBar';
33
import OpenKeyItem from '@/components/openKey/openKey';
44
import LoadingPlaceholder from '@/components/others/LoadingPlaceholder';
5-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
5+
import UserAvatar from '@/components/others/UserAvatar';
66
import { Button } from '@/components/ui/button';
77
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
88
import { Input } from '@/components/ui/input';
@@ -15,16 +15,7 @@ import { API_URL, get, post } from '@/utils/api';
1515
import { useAPIGet } from '@/utils/fetcher';
1616
import { useAtomValue, useSetAtom } from 'jotai';
1717
import Linkify from 'linkify-react';
18-
import {
19-
Edit,
20-
KeyRoundIcon,
21-
Loader,
22-
LoaderPinwheel,
23-
Rss,
24-
Stars,
25-
User,
26-
UserCircle2,
27-
} from 'lucide-react';
18+
import { Edit, KeyRoundIcon, Loader, LoaderPinwheel, Rss, Stars, UserCircle2 } from 'lucide-react';
2819
import moment from 'moment';
2920
import { useCallback, useEffect, useRef, useState } from 'react';
3021
import type { Area } from 'react-easy-crop';
@@ -299,21 +290,15 @@ function ProfilePage() {
299290
</div>
300291
</div>
301292
<div className="mx-4 flex h-16 items-center">
302-
<Avatar
293+
<UserAvatar
294+
avatar={profile?.avatar}
303295
className="text-primary size-20 shrink-0 translate-y-[-50%] cursor-pointer border-[4px] sm:block"
296+
fallbackClassName="bg-muted/80"
304297
onClick={() => {
305298
if (!canUpload) return;
306299
(inputAvatarRef.current as HTMLInputElement | null)?.click();
307300
}}
308-
>
309-
{profile?.avatar ? (
310-
<AvatarImage src={profile.avatar} />
311-
) : (
312-
<AvatarFallback className="bg-muted/80">
313-
<User className="text-foreground/10 size-12" />
314-
</AvatarFallback>
315-
)}
316-
</Avatar>
301+
/>
317302
<Button
318303
className="ml-auto"
319304
onClick={() => {
@@ -387,21 +372,15 @@ function ProfilePage() {
387372
title="Upload avatar image"
388373
/>
389374
{/* 编辑弹窗内头像同理 */}
390-
<Avatar
375+
<UserAvatar
376+
avatar={editProfile?.avatar}
391377
className="text-primary mx-auto my-2 block size-20 shrink-0 cursor-pointer bg-[#00000010]"
378+
fallbackClassName="bg-muted/80"
392379
onClick={() => {
393380
if (!canUpload) return;
394381
(inputAvatarRef.current as HTMLInputElement | null)?.click();
395382
}}
396-
>
397-
{editProfile?.avatar ? (
398-
<AvatarImage src={editProfile.avatar} />
399-
) : (
400-
<AvatarFallback className="bg-muted/80">
401-
<User className="text-foreground/10 size-12" />
402-
</AvatarFallback>
403-
)}
404-
</Avatar>
383+
/>
405384
<div className="mt-2 text-base font-semibold">{t('email')}</div>
406385
<Input
407386
disabled

web/src/pages/rote/:roteid.tsx

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import NavBar from '@/components/layout/navBar';
22
import LoadingPlaceholder from '@/components/others/LoadingPlaceholder';
3+
import UserAvatar from '@/components/others/UserAvatar';
34
import RoteItem from '@/components/rote/roteItem';
4-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
55
import ContainerWithSideBar from '@/layout/ContainerWithSideBar';
66

77
import type { Rote } from '@/types/main';
88
import { API_URL, get } from '@/utils/api';
99
import { useAPIGet } from '@/utils/fetcher';
10-
import { Navigation, RefreshCw, Rss, User } from 'lucide-react';
10+
import { Navigation, RefreshCw, Rss } from 'lucide-react';
1111
import { useEffect } from 'react';
1212
import { useTranslation } from 'react-i18next';
1313
import { Link, useNavigate, useParams } from 'react-router-dom';
@@ -51,15 +51,10 @@ function SingleRotePage() {
5151
<div className="border-b p-4">
5252
<Link to={`/${rote.author.username}`} className="block">
5353
<div className="flex items-center gap-3">
54-
<Avatar className="bg-foreground/5 text-primary size-12">
55-
{rote.author.avatar ? (
56-
<AvatarImage src={rote.author.avatar} />
57-
) : (
58-
<AvatarFallback>
59-
<User className="text-info size-6" />
60-
</AvatarFallback>
61-
)}
62-
</Avatar>
54+
<UserAvatar
55+
avatar={rote.author.avatar}
56+
className="bg-foreground/5 text-primary size-12"
57+
/>
6358
<div className="min-w-0 flex-1">
6459
<div className="text-primary truncate font-semibold">{rote.author.nickname}</div>
6560
<div className="text-info truncate text-sm">@{rote.author.username}</div>

web/src/pages/user/:username.tsx

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import defaultCover from '@/assets/img/defaultCover.png';
22
import NavBar from '@/components/layout/navBar';
33
import LoadingPlaceholder from '@/components/others/LoadingPlaceholder';
4+
import UserAvatar from '@/components/others/UserAvatar';
45
import RoteList from '@/components/rote/roteList';
5-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
66
import ContainerWithSideBar from '@/layout/ContainerWithSideBar';
77
import type { ApiGetRotesParams, Profile, Rotes } from '@/types/main';
88
import { API_URL, get } from '@/utils/api';
99
import { useAPIGet, useAPIInfinite } from '@/utils/fetcher';
1010
import { getRotesV2 } from '@/utils/roteApi';
1111
import { Helmet } from '@dr.pogodin/react-helmet';
1212
import Linkify from 'linkify-react';
13-
import { Globe2, RefreshCw, Rss, Stars, User } from 'lucide-react';
13+
import { Globe2, RefreshCw, Rss, Stars } from 'lucide-react';
1414
import moment from 'moment';
1515
import { useTranslation } from 'react-i18next';
1616
import { useNavigate, useParams } from 'react-router-dom';
@@ -110,15 +110,10 @@ function UserPage() {
110110
<NavBar
111111
title={
112112
<>
113-
<Avatar className="bg-background size-6 shrink-0 border sm:block">
114-
{userInfo?.avatar ? (
115-
<AvatarImage src={userInfo.avatar} />
116-
) : (
117-
<AvatarFallback>
118-
<User className="size-4 text-[#00000010]" />
119-
</AvatarFallback>
120-
)}
121-
</Avatar>
113+
<UserAvatar
114+
avatar={userInfo?.avatar}
115+
className="bg-background size-6 shrink-0 border sm:block"
116+
/>
122117
<span>{userInfo?.nickname || userInfo?.username}</span>
123118
</>
124119
}
@@ -139,15 +134,11 @@ function UserPage() {
139134
</div>
140135
<div className="mx-4 flex h-16">
141136
{/* 主页顶部头像展示,shadcn Avatar 不支持 size 属性,直接用 className 控制尺寸 */}
142-
<Avatar className="bg-background size-20 shrink-0 translate-y-[-50%] border-[4px] sm:block">
143-
{userInfo?.avatar ? (
144-
<AvatarImage src={userInfo.avatar} />
145-
) : (
146-
<AvatarFallback className="bg-muted/80">
147-
<User className="text-foreground/10 size-12" />
148-
</AvatarFallback>
149-
)}
150-
</Avatar>
137+
<UserAvatar
138+
avatar={userInfo?.avatar}
139+
className="bg-background size-20 shrink-0 translate-y-[-50%] border-[4px] sm:block"
140+
fallbackClassName="bg-muted/80"
141+
/>
151142
</div>
152143
<div className="mx-4 flex flex-col gap-1">
153144
<div className="text-2xl font-semibold">{userInfo?.nickname}</div>

0 commit comments

Comments
 (0)