From 54ebe1ba3d612b02201250aeb2e5772f41fe4ce6 Mon Sep 17 00:00:00 2001 From: Yann <1319542051@qq.com> Date: Wed, 15 Apr 2026 16:36:24 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E5=9B=9E=E6=94=B6?= =?UTF-8?q?=E7=AB=99=E7=82=B9=E5=87=BB=E6=B8=85=E7=A9=BA=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E5=90=8E=E6=96=87=E4=BB=B6=E5=88=97=E8=A1=A8=E6=9C=AA=E5=88=B7?= =?UTF-8?q?=E6=96=B0=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/files/components/RecycleBinView.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/pages/files/components/RecycleBinView.tsx b/src/pages/files/components/RecycleBinView.tsx index aefb93f..d56f960 100644 --- a/src/pages/files/components/RecycleBinView.tsx +++ b/src/pages/files/components/RecycleBinView.tsx @@ -166,16 +166,20 @@ export default function RecycleBinView() { } const confirmClearRecycle = async () => { - setLoading(true) try { await clearRecycle() - toast.success('回收站已清空') + // 乐观更新:先清空本地数据,避免 loading 状态导致的闪烁 + setFileList([]) + setTotal(0) setClearDialogOpen(false) setSelectedIds([]) commitSearch('') setPagination((p) => ({ ...p, pageIndex: 0 })) - } finally { - setLoading(false) + toast.success('回收站已清空') + void fetchRecyclePage() + } catch { + // 失败时重新拉取,恢复真实数据 + void fetchRecyclePage() } } -- Gitee From 8089107edede21d97b2e03c6fa15125695f1dbe2 Mon Sep 17 00:00:00 2001 From: Yann <1319542051@qq.com> Date: Wed, 15 Apr 2026 17:08:33 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=E6=A1=86=E6=80=A7=E8=83=BD=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.tsx | 28 ++--- src/components/confirm-dialog.tsx | 44 +++---- src/pages/files/components/MySharesView.tsx | 110 +++++++++--------- src/pages/files/components/RecycleBinView.tsx | 106 +++++++++-------- 4 files changed, 152 insertions(+), 136 deletions(-) diff --git a/src/app.tsx b/src/app.tsx index 1f9a1f1..62fe976 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -47,19 +47,21 @@ function LogoutDialog() { return ( - - - 登录已过期 - - 您的登录状态已过期,请重新登录以继续使用。 - - - - - 返回登录 - - - + {showLogoutDialog && ( + + + 登录已过期 + + 您的登录状态已过期,请重新登录以继续使用。 + + + + + 返回登录 + + + + )} ) } diff --git a/src/components/confirm-dialog.tsx b/src/components/confirm-dialog.tsx index 2db6b25..0a0344c 100644 --- a/src/components/confirm-dialog.tsx +++ b/src/components/confirm-dialog.tsx @@ -41,27 +41,29 @@ export function ConfirmDialog(props: ConfirmDialogProps) { } = props return ( - - - {title} - -
{desc}
-
-
- {children} - - - {cancelBtnText ?? 'Cancel'} - - - -
+ {actions.open && ( + + + {title} + +
{desc}
+
+
+ {children} + + + {cancelBtnText ?? 'Cancel'} + + + +
+ )}
) } diff --git a/src/pages/files/components/MySharesView.tsx b/src/pages/files/components/MySharesView.tsx index 1dc78a0..42d715c 100644 --- a/src/pages/files/components/MySharesView.tsx +++ b/src/pages/files/components/MySharesView.tsx @@ -1019,23 +1019,25 @@ export function MySharesView() { open={deleteDialogVisible} onOpenChange={setDeleteDialogVisible} > - - - 确认取消分享 - - 确定要取消分享 "{deletingShare?.shareName}" 吗?取消后将无法恢复! - - - - 取消 - - 确认 - - - + {deleteDialogVisible && ( + + + 确认取消分享 + + 确定要取消分享 "{deletingShare?.shareName}" 吗?取消后将无法恢复! + + + + 取消 + + 确认 + + + + )} {/* 清空所有分享确认 */} @@ -1043,23 +1045,25 @@ export function MySharesView() { open={clearAllDialogVisible} onOpenChange={setClearAllDialogVisible} > - - - 确认清空所有分享 - - 确定要清空所有分享吗?所有分享链接将失效且无法恢复! - - - - 取消 - - 清空 - - - + {clearAllDialogVisible && ( + + + 确认清空所有分享 + + 确定要清空所有分享吗?所有分享链接将失效且无法恢复! + + + + 取消 + + 清空 + + + + )} {/* 批量删除确认弹窗 */} @@ -1067,24 +1071,26 @@ export function MySharesView() { open={batchDeleteDialogVisible} onOpenChange={setBatchDeleteDialogVisible} > - - - 确认批量取消 - - 确定要取消选中的 {selectedKeys.length}{' '} - 个分享吗?取消后将无法恢复! - - - - 取消 - - 确认 - - - + {batchDeleteDialogVisible && ( + + + 确认批量取消 + + 确定要取消选中的 {selectedKeys.length}{' '} + 个分享吗?取消后将无法恢复! + + + + 取消 + + 确认 + + + + )} ) diff --git a/src/pages/files/components/RecycleBinView.tsx b/src/pages/files/components/RecycleBinView.tsx index d56f960..b420c0b 100644 --- a/src/pages/files/components/RecycleBinView.tsx +++ b/src/pages/files/components/RecycleBinView.tsx @@ -574,62 +574,68 @@ export default function RecycleBinView() { )} - - - 确认还原 - - {operatingItem - ? `确定要还原文件 "${operatingItem.name}" 吗?` - : `确定要还原选中的 ${selectedIds.length} 个文件吗?`} - - - - 取消 - 还原 - - + {restoreDialogOpen && ( + + + 确认还原 + + {operatingItem + ? `确定要还原文件 "${operatingItem.name}" 吗?` + : `确定要还原选中的 ${selectedIds.length} 个文件吗?`} + + + + 取消 + 还原 + + + )} - - - 确认彻底删除 - - {operatingItem - ? `确定要彻底删除文件 "${operatingItem.name}" 吗?删除后将无法恢复!` - : `确定要彻底删除选中的 ${selectedIds.length} 个文件吗?删除后将无法恢复!`} - - - - 取消 - - 删除 - - - + {deleteDialogOpen && ( + + + 确认彻底删除 + + {operatingItem + ? `确定要彻底删除文件 "${operatingItem.name}" 吗?删除后将无法恢复!` + : `确定要彻底删除选中的 ${selectedIds.length} 个文件吗?删除后将无法恢复!`} + + + + 取消 + + 删除 + + + + )} - - - 确认清空回收站 - - 确定要清空回收站吗?所有文件将被彻底删除且无法恢复! - - - - 取消 - - 清空 - - - + {clearDialogOpen && ( + + + 确认清空回收站 + + 确定要清空回收站吗?所有文件将被彻底删除且无法恢复! + + + + 取消 + + 清空 + + + + )} ) -- Gitee From 5051b35e9039b81a10bf27bfeac2ac053bb1bfb3 Mon Sep 17 00:00:00 2001 From: Yann <1319542051@qq.com> Date: Wed, 15 Apr 2026 17:27:38 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E6=93=8D=E4=BD=9C?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=88=97=E8=A1=A8=E6=9B=B4=E6=96=B0=E9=83=A8?= =?UTF-8?q?=E5=88=86=E5=AD=97=E6=AE=B5=E6=97=B6=E9=81=BF=E5=85=8D=E4=B8=8D?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E5=88=97=E8=A1=A8=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/files/components/FileGridView.tsx | 21 ++++++++++----------- src/pages/files/hooks/useFileList.ts | 11 +++++++++++ src/pages/files/hooks/useFileOperations.ts | 15 +++++++++++---- src/pages/files/index.tsx | 2 +- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/pages/files/components/FileGridView.tsx b/src/pages/files/components/FileGridView.tsx index 2f5e3b0..71e6991 100644 --- a/src/pages/files/components/FileGridView.tsx +++ b/src/pages/files/components/FileGridView.tsx @@ -86,6 +86,12 @@ export function FileGridView({ }: FileGridViewProps) { const [openMenuId, setOpenMenuId] = useState(null) + // 提到 map 外部,避免每个 item 重复计算 + const selectedSet = new Set(selectedKeys) + const selectedFiles = fileList.filter((f) => selectedSet.has(f.id)) + const hasUnfavorited = selectedFiles.some((f) => !f.isFavorite) + const downloadableFiles = selectedFiles.filter((f) => !f.isDir) + // 拖拽功能 const { dragState, @@ -102,11 +108,9 @@ export function FileGridView({ if (onDragStateChange) { onDragStateChange(dragState.dropTargetName, dragState.draggedItems.length) } - }, [ - dragState.dropTargetName, - dragState.draggedItems.length, - onDragStateChange, - ]) + // onDragStateChange 是父组件传入的稳定引用,不加入依赖以避免无效重跑 + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dragState.dropTargetName, dragState.draggedItems.length]) const handleItemClick = (file: FileItem, event: React.MouseEvent) => { const isMultiSelect = event.ctrlKey || event.metaKey @@ -146,17 +150,12 @@ export function FileGridView({
{fileList.map((file) => { - const isSelected = selectedKeys.includes(file.id) + const isSelected = selectedSet.has(file.id) const isDragging = dragState.draggedItems.some( (f) => f.id === file.id ) const isDropTarget = file.isDir && dragState.dropTargetId === file.id const isMultiSelected = selectedKeys.length > 1 && isSelected - const selectedFiles = fileList.filter((f) => - selectedKeys.includes(f.id) - ) - const hasUnfavorited = selectedFiles.some((f) => !f.isFavorite) - const downloadableFiles = selectedFiles.filter((f) => !f.isDir) return ( diff --git a/src/pages/files/hooks/useFileList.ts b/src/pages/files/hooks/useFileList.ts index a37358f..a158b69 100644 --- a/src/pages/files/hooks/useFileList.ts +++ b/src/pages/files/hooks/useFileList.ts @@ -209,6 +209,16 @@ export function useFileList() { fetchInitial() }, [fetchInitial]) + /** 本地更新部分文件字段,避免不必要的列表刷新 */ + const updateFileItems = useCallback( + (ids: string[], patch: Partial) => { + setFileList((prev) => + prev.map((f) => (ids.includes(f.id) ? { ...f, ...patch } : f)) + ) + }, + [] + ) + const handleSortChange = useCallback( (field: string, direction: SortOrder) => { setOrderBy(field) @@ -263,6 +273,7 @@ export function useFileList() { enterFolder, navigateToFolder, refresh, + updateFileItems, commitSearch, handleSortChange, } diff --git a/src/pages/files/hooks/useFileOperations.ts b/src/pages/files/hooks/useFileOperations.ts index c401e88..256ccb3 100644 --- a/src/pages/files/hooks/useFileOperations.ts +++ b/src/pages/files/hooks/useFileOperations.ts @@ -14,7 +14,8 @@ import { openFilePreviewWithToken } from '@/utils/preview' export function useFileOperations( refreshCallback: () => void, clearSelectionCallback?: () => void, - onCreateFolderSuccess?: () => void + onCreateFolderSuccess?: () => void, + updateFileItemsCallback?: (ids: string[], patch: Partial) => void ) { // 模态框状态 const [createFolderModalVisible, setCreateFolderModalVisible] = @@ -213,6 +214,7 @@ export function useFileOperations( /** * 收藏/取消收藏 + * 使用乐观更新:直接修改本地状态,失败时回滚刷新列表 */ const handleFavorite = useCallback( async (files: FileItem | FileItem[]) => { @@ -221,6 +223,11 @@ export function useFileOperations( // 判断是收藏还是取消收藏(如果有任何一个未收藏,就执行收藏操作) const hasUnfavorited = fileArray.some((f) => !f.isFavorite) + const newFavoriteState = hasUnfavorited + + // 乐观更新本地状态 + updateFileItemsCallback?.(fileIds, { isFavorite: newFavoriteState }) + clearSelectionCallback?.() try { if (hasUnfavorited) { @@ -230,13 +237,13 @@ export function useFileOperations( await unfavoriteFile(fileIds) toast.success('取消收藏成功') } - clearSelectionCallback?.() - refreshCallback() } catch (error) { + // 失败时回滚:刷新列表恢复真实状态 + updateFileItemsCallback?.(fileIds, { isFavorite: !newFavoriteState }) toast.error(hasUnfavorited ? '收藏失败' : '取消收藏失败') } }, - [refreshCallback, clearSelectionCallback] + [clearSelectionCallback, updateFileItemsCallback] ) /** diff --git a/src/pages/files/index.tsx b/src/pages/files/index.tsx index 21c6745..f58ff33 100644 --- a/src/pages/files/index.tsx +++ b/src/pages/files/index.tsx @@ -89,7 +89,7 @@ export default function FilesPage() { if (isFavoritesView || isRecentsView || isTypeFilter || isDirFilter) { navigate(`/files?viewMode=${viewMode}`) } - }) + }, fileList.updateFileItems) // 计算当前视图类型 const viewType = searchParams.get('view') -- Gitee From 5b1aec2604d4d0161a8ea1df7dcadbc38c6927eb Mon Sep 17 00:00:00 2001 From: Yann <1319542051@qq.com> Date: Thu, 16 Apr 2026 00:08:01 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E8=AD=A6=E5=91=8A=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ui/dialog.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index c6e95c2..6ece881 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -36,11 +36,13 @@ type DialogContentProps = React.ComponentPropsWithoutRef< const DialogContent = React.forwardRef< React.ElementRef, DialogContentProps ->(({ className, children, hideClose, ...props }, ref) => ( +>(({ className, children, hideClose, 'aria-describedby': ariaDescribedby, ...props }, ref) => (