From b643205f72c0be04064b93e54f8e488f2a1c2a71 Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Sun, 8 Mar 2026 15:07:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(folders):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E9=80=BB=E8=BE=91=EF=BC=8C=E5=8F=AA=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=89=B9=E5=AE=9A=E6=96=87=E4=BB=B6=E5=A4=B9=E8=80=8C?= =?UTF-8?q?=E9=9D=9E=E5=85=A8=E9=87=8F=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FoldersClient: 使用 onUpdateFolder/onDeleteFolder 回调局部更新 - ExploreClient: 使用 onUpdateFavorite 只更新收藏数 - FavoritesClient: 使用 onRemoveFavorite 从列表移除,避免重新请求 --- src/app/(features)/explore/ExploreClient.tsx | 21 ++- .../(features)/favorites/FavoritesClient.tsx | 73 +++++++---- src/app/folders/FoldersClient.tsx | 124 ++++++++++-------- 3 files changed, 126 insertions(+), 92 deletions(-) diff --git a/src/app/(features)/explore/ExploreClient.tsx b/src/app/(features)/explore/ExploreClient.tsx index 9e78499..50711ea 100644 --- a/src/app/(features)/explore/ExploreClient.tsx +++ b/src/app/(features)/explore/ExploreClient.tsx @@ -23,10 +23,10 @@ import { authClient } from "@/lib/auth-client"; interface PublicFolderCardProps { folder: TPublicFolder; currentUserId?: string; - onFavoriteChange?: () => void; + onUpdateFavorite: (folderId: number, isFavorited: boolean, favoriteCount: number) => void; } -const PublicFolderCard = ({ folder, currentUserId, onFavoriteChange }: PublicFolderCardProps) => { +const PublicFolderCard = ({ folder, currentUserId, onUpdateFavorite }: PublicFolderCardProps) => { const router = useRouter(); const t = useTranslations("explore"); const [isFavorited, setIsFavorited] = useState(false); @@ -53,7 +53,7 @@ const PublicFolderCard = ({ folder, currentUserId, onFavoriteChange }: PublicFol if (result.success && result.data) { setIsFavorited(result.data.isFavorited); setFavoriteCount(result.data.favoriteCount); - onFavoriteChange?.(); + onUpdateFavorite(folder.id, result.data.isFavorited, result.data.favoriteCount); } else { toast.error(result.message); } @@ -128,13 +128,12 @@ export function ExploreClient({ initialPublicFolders }: ExploreClientProps) { setLoading(false); }; - const refreshFolders = async () => { - setLoading(true); - const result = await actionSearchPublicFolders(searchQuery.trim() || ""); - if (result.success && result.data) { - setPublicFolders(result.data); - } - setLoading(false); + const handleUpdateFavorite = (folderId: number, _isFavorited: boolean, favoriteCount: number) => { + setPublicFolders((prev) => + prev.map((f) => + f.id === folderId ? { ...f, favoriteCount } : f + ) + ); }; return ( @@ -177,7 +176,7 @@ export function ExploreClient({ initialPublicFolders }: ExploreClientProps) { key={folder.id} folder={folder} currentUserId={currentUserId} - onFavoriteChange={refreshFolders} + onUpdateFavorite={handleUpdateFavorite} /> ))} diff --git a/src/app/(features)/favorites/FavoritesClient.tsx b/src/app/(features)/favorites/FavoritesClient.tsx index 0b53455..a7963a5 100644 --- a/src/app/(features)/favorites/FavoritesClient.tsx +++ b/src/app/(features)/favorites/FavoritesClient.tsx @@ -8,10 +8,11 @@ import { import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; +import { toast } from "sonner"; import { PageLayout } from "@/components/ui/PageLayout"; import { PageHeader } from "@/components/ui/PageHeader"; import { CardList } from "@/components/ui/CardList"; -import { actionGetUserFavorites } from "@/modules/folder/folder-aciton"; +import { actionGetUserFavorites, actionToggleFavorite } from "@/modules/folder/folder-aciton"; type UserFavorite = { id: number; @@ -27,11 +28,27 @@ type UserFavorite = { interface FavoriteCardProps { favorite: UserFavorite; + onRemoveFavorite: (folderId: number) => void; } -const FavoriteCard = ({ favorite }: FavoriteCardProps) => { +const FavoriteCard = ({ favorite, onRemoveFavorite }: FavoriteCardProps) => { const router = useRouter(); const t = useTranslations("favorites"); + const [isRemoving, setIsRemoving] = useState(false); + + const handleRemoveFavorite = async (e: React.MouseEvent) => { + e.stopPropagation(); + if (isRemoving) return; + + setIsRemoving(true); + const result = await actionToggleFavorite(favorite.folderId); + if (result.success) { + onRemoveFavorite(favorite.folderId); + } else { + toast.error(result.message); + } + setIsRemoving(false); + }; return (
{
- +
@@ -86,31 +107,37 @@ export function FavoritesClient({ userId }: FavoritesClientProps) { setLoading(false); }; + const handleRemoveFavorite = (folderId: number) => { + setFavorites((prev) => prev.filter((f) => f.folderId !== folderId)); + }; + return ( -
- - {loading ? ( -
-
-

{t("loading")}

+ + {loading ? ( +
+
+

{t("loading")}

+
+ ) : favorites.length === 0 ? ( +
+
+
- ) : favorites.length === 0 ? ( -
-
- -
-

{t("noFavorites")}

-
- ) : ( - favorites.map((favorite) => ( - - )) - )} - -
+

{t("noFavorites")}

+
+ ) : ( + favorites.map((favorite) => ( + + )) + )} +
); } diff --git a/src/app/folders/FoldersClient.tsx b/src/app/folders/FoldersClient.tsx index 341e5f3..f33fd42 100644 --- a/src/app/folders/FoldersClient.tsx +++ b/src/app/folders/FoldersClient.tsx @@ -28,10 +28,11 @@ import { TSharedFolderWithTotalPairs } from "@/shared/folder-type"; interface FolderCardProps { folder: TSharedFolderWithTotalPairs; - refresh: () => void; + onUpdateFolder: (folderId: number, updates: Partial) => void; + onDeleteFolder: (folderId: number) => void; } -const FolderCard = ({ folder, refresh }: FolderCardProps) => { +const FolderCard = ({ folder, onUpdateFolder, onDeleteFolder }: FolderCardProps) => { const router = useRouter(); const t = useTranslations("folders"); @@ -40,12 +41,38 @@ const FolderCard = ({ folder, refresh }: FolderCardProps) => { const newVisibility = folder.visibility === "PUBLIC" ? "PRIVATE" : "PUBLIC"; const result = await actionSetFolderVisibility(folder.id, newVisibility); if (result.success) { - refresh(); + onUpdateFolder(folder.id, { visibility: newVisibility }); } else { toast.error(result.message); } }; + const handleRename = async (e: React.MouseEvent) => { + e.stopPropagation(); + const newName = prompt(t("enterNewName"))?.trim(); + if (newName && newName.length > 0) { + const result = await actionRenameFolderById(folder.id, newName); + if (result.success) { + onUpdateFolder(folder.id, { name: newName }); + } else { + toast.error(result.message); + } + } + }; + + const handleDelete = async (e: React.MouseEvent) => { + e.stopPropagation(); + const confirm = prompt(t("confirmDelete", { name: folder.name })); + if (confirm === folder.name) { + const result = await actionDeleteFolderById(folder.id); + if (result.success) { + onDeleteFolder(folder.id); + } else { + toast.error(result.message); + } + } + }; + return (
{ )} - { - e.stopPropagation(); - const newName = prompt(t("enterNewName"))?.trim(); - if (newName && newName.length > 0) { - actionRenameFolderById(folder.id, newName).then((result) => { - if (result.success) { - refresh(); - } else { - toast.error(result.message); - } - }); - } - }} - > + { - e.stopPropagation(); - const confirm = prompt(t("confirmDelete", { name: folder.name })); - if (confirm === folder.name) { - actionDeleteFolderById(folder.id).then((result) => { - if (result.success) { - refresh(); - } else { - toast.error(result.message); - } - }); - } - }} - className="text-gray-400 hover:text-red-500 hover:bg-red-50" + onClick={handleDelete} + className="hover:text-red-500 hover:bg-red-50" > - +
); @@ -155,19 +156,25 @@ export function FoldersClient({ userId }: FoldersClientProps) { setLoading(false); }; + const handleUpdateFolder = (folderId: number, updates: Partial) => { + setFolders((prev) => + prev.map((f) => (f.id === folderId ? { ...f, ...updates } : f)) + ); + }; + + const handleDeleteFolder = (folderId: number) => { + setFolders((prev) => prev.filter((f) => f.id !== folderId)); + }; + const handleCreateFolder = async () => { const folderName = prompt(t("enterFolderName")); - if (!folderName) return; - setLoading(true); - try { - const result = await actionCreateFolder(userId, folderName); - if (result.success) { - loadFolders(); - } else { - toast.error(result.message); - } - } finally { - setLoading(false); + if (!folderName?.trim()) return; + + const result = await actionCreateFolder(userId, folderName.trim()); + if (result.success) { + loadFolders(); + } else { + toast.error(result.message); } }; @@ -175,14 +182,12 @@ export function FoldersClient({ userId }: FoldersClientProps) { - - - {loading ? t("creating") : t("newFolder")} - +
+ + + {t("newFolder")} + +
{loading ? ( @@ -193,16 +198,19 @@ export function FoldersClient({ userId }: FoldersClientProps) { ) : folders.length === 0 ? (
- +

{t("noFoldersYet")}

) : ( - folders - .toSorted((a, b) => b.id - a.id) - .map((folder) => ( - - )) + folders.map((folder) => ( + + )) )}