refactor(platform): extract blurActiveElement to infrastructure module
- Centralize duplicated Platform.OS === 'web' blur logic across 16 components - Add blurActiveElement utility to src/infrastructure/platform module - Optimize MessageBubble and ImageSegment with React.memo custom comparators - Add useMemo for computed values in MessageSegmentsRenderer - Update DTO types with is_system_notice and notice_content fields - Fix keyboard dismissal order in useChatScreen handleDismiss - Simplify userStore follow/unfollow state updates - Remove unnecessary TypeScript type assertions throughout codebase
This commit is contained in:
@@ -38,6 +38,7 @@ import { processPostUseCase } from '../../core/usecases/ProcessPostUseCase';
|
||||
import { SearchScreen } from './SearchScreen';
|
||||
import { CreatePostScreen } from '../create/CreatePostScreen';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
import { blurActiveElement } from '../../infrastructure/platform';
|
||||
|
||||
const TABS = ['关注', '最新', '热门'];
|
||||
const TAB_ICONS = ['account-heart-outline', 'clock-outline', 'fire'];
|
||||
@@ -203,9 +204,8 @@ export const HomeScreen: React.FC = () => {
|
||||
const [showCreatePost, setShowCreatePost] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (showCreatePost && Platform.OS === 'web') {
|
||||
const active = (globalThis as any)?.document?.activeElement as { blur?: () => void } | undefined;
|
||||
active?.blur?.();
|
||||
if (showCreatePost) {
|
||||
blurActiveElement();
|
||||
}
|
||||
}, [showCreatePost]);
|
||||
|
||||
|
||||
@@ -42,8 +42,35 @@ import { useCursorPagination } from '../../hooks/useCursorPagination';
|
||||
import { CommentItem, VoteCard, ReportDialog } from '../../components/business';
|
||||
import { Avatar, Button, Loading, EmptyState, Text, ImageGallery, ImageGrid, ImageGridItem, AdaptiveLayout, AppBackButton } from '../../components/common';
|
||||
import { useResponsive, useResponsiveValue, useResponsiveSpacing } from '../../hooks/useResponsive';
|
||||
import { handleError } from '../../services/errorHandler';
|
||||
import * as hrefs from '../../navigation/hrefs';
|
||||
|
||||
function togglePostField(
|
||||
post: Post,
|
||||
field: 'is_liked' | 'is_favorited',
|
||||
countField: 'likes_count' | 'favorites_count',
|
||||
): Post {
|
||||
const oldValue = post[field];
|
||||
const oldCount = post[countField];
|
||||
return {
|
||||
...post,
|
||||
[field]: !oldValue,
|
||||
[countField]: oldValue ? Math.max(0, oldCount - 1) : oldCount + 1,
|
||||
};
|
||||
}
|
||||
|
||||
function toggleCommentLikeInTree(comments: Comment[], commentId: string, isLiked: boolean, likesCount: number): Comment[] {
|
||||
return comments.map(c => {
|
||||
if (c.id === commentId) {
|
||||
return { ...c, is_liked: isLiked, likes_count: likesCount };
|
||||
}
|
||||
if (c.replies?.length) {
|
||||
return { ...c, replies: toggleCommentLikeInTree(c.replies, commentId, isLiked, likesCount) };
|
||||
}
|
||||
return c;
|
||||
});
|
||||
}
|
||||
|
||||
export const PostDetailScreen: React.FC = () => {
|
||||
const colors = useAppColors();
|
||||
const styles = useMemo(() => createPostDetailStyles(colors), [colors]);
|
||||
@@ -217,20 +244,17 @@ export const PostDetailScreen: React.FC = () => {
|
||||
setPost(postData as unknown as Post);
|
||||
// 初始化关注状态
|
||||
if (postData.author) {
|
||||
setIsFollowing((postData.author as any).is_following || false);
|
||||
setIsFollowingMe((postData.author as any).is_following_me || false);
|
||||
setIsFollowing(postData.author.is_following || false);
|
||||
setIsFollowingMe(postData.author.is_following_me || false);
|
||||
}
|
||||
// 只在首次加载时记录浏览量
|
||||
if (recordView && !hasRecordedView.current) {
|
||||
if (!hasRecordedView.current) {
|
||||
hasRecordedView.current = true;
|
||||
// 异步记录浏览量,不阻塞加载
|
||||
postService.recordView(postId).catch(err => {
|
||||
console.error('记录浏览量失败:', err);
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是投票帖子,立即加载投票数据
|
||||
if ((postData as any).is_vote) {
|
||||
if (postData.is_vote) {
|
||||
setIsVoteLoading(true);
|
||||
try {
|
||||
const voteData = await voteService.getVoteResult(postId);
|
||||
@@ -251,8 +275,8 @@ export const PostDetailScreen: React.FC = () => {
|
||||
setPost(updatedPost);
|
||||
// 初始化关注状态
|
||||
if (updatedPost.author) {
|
||||
setIsFollowing((updatedPost.author as any).is_following || false);
|
||||
setIsFollowingMe((updatedPost.author as any).is_following_me || false);
|
||||
setIsFollowing(updatedPost.author.is_following || false);
|
||||
setIsFollowingMe(updatedPost.author.is_following_me || false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +284,7 @@ export const PostDetailScreen: React.FC = () => {
|
||||
// 加载评论(使用游标分页刷新)
|
||||
await refreshComments();
|
||||
} catch (error) {
|
||||
console.error('加载帖子详情失败:', error);
|
||||
handleError(error, { context: '加载帖子详情' });
|
||||
} finally {
|
||||
setIsPostInitialLoading(false);
|
||||
}
|
||||
@@ -458,65 +482,36 @@ export const PostDetailScreen: React.FC = () => {
|
||||
const handleLike = useCallback(async () => {
|
||||
if (!post) return;
|
||||
|
||||
// 先保存旧状态用于回滚
|
||||
const oldIsLiked = post.is_liked;
|
||||
const oldLikesCount = post.likes_count;
|
||||
|
||||
// 乐观更新本地状态
|
||||
setPost(prev => prev ? {
|
||||
...prev,
|
||||
is_liked: !prev.is_liked,
|
||||
likes_count: prev.is_liked ? prev.likes_count - 1 : prev.likes_count + 1
|
||||
} : null);
|
||||
const originalPost = post;
|
||||
setPost(prev => prev ? togglePostField(prev, 'is_liked', 'likes_count') : null);
|
||||
|
||||
try {
|
||||
if (oldIsLiked) {
|
||||
if (originalPost.is_liked) {
|
||||
await processPostUseCase.unlikePost(post.id);
|
||||
} else {
|
||||
await processPostUseCase.likePost(post.id);
|
||||
}
|
||||
// UseCase 会更新 store,本地状态已经是乐观更新的,无需再次更新
|
||||
} catch (error) {
|
||||
console.error('点赞操作失败:', error);
|
||||
// 失败时回滚状态
|
||||
setPost(prev => prev ? {
|
||||
...prev,
|
||||
is_liked: oldIsLiked,
|
||||
likes_count: oldLikesCount
|
||||
} : null);
|
||||
handleError(error, { context: '点赞' });
|
||||
setPost(originalPost);
|
||||
}
|
||||
}, [post]);
|
||||
|
||||
// 收藏帖子
|
||||
const handleFavorite = useCallback(async () => {
|
||||
if (!post) return;
|
||||
|
||||
// 先保存旧状态用于回滚
|
||||
const oldIsFavorited = post.is_favorited;
|
||||
const oldFavoritesCount = post.favorites_count;
|
||||
|
||||
// 乐观更新本地状态
|
||||
setPost(prev => prev ? {
|
||||
...prev,
|
||||
is_favorited: !prev.is_favorited,
|
||||
favorites_count: prev.is_favorited ? prev.favorites_count - 1 : prev.favorites_count + 1
|
||||
} : null);
|
||||
const originalPost = post;
|
||||
setPost(prev => prev ? togglePostField(prev, 'is_favorited', 'favorites_count') : null);
|
||||
|
||||
try {
|
||||
if (oldIsFavorited) {
|
||||
if (originalPost.is_favorited) {
|
||||
await processPostUseCase.unfavoritePost(post.id);
|
||||
} else {
|
||||
await processPostUseCase.favoritePost(post.id);
|
||||
}
|
||||
// UseCase 会更新 store,本地状态已经是乐观更新的,无需再次更新
|
||||
} catch (error) {
|
||||
console.error('收藏操作失败:', error);
|
||||
// 失败时回滚状态
|
||||
setPost(prev => prev ? {
|
||||
...prev,
|
||||
is_favorited: oldIsFavorited,
|
||||
favorites_count: oldFavoritesCount
|
||||
} : null);
|
||||
handleError(error, { context: '收藏' });
|
||||
setPost(originalPost);
|
||||
}
|
||||
}, [post]);
|
||||
|
||||
@@ -573,24 +568,19 @@ export const PostDetailScreen: React.FC = () => {
|
||||
setVoteResult(oldVoteResult);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('投票失败:', error);
|
||||
// 失败时回滚
|
||||
handleError(error, { context: '投票' });
|
||||
setVoteResult(oldVoteResult);
|
||||
} finally {
|
||||
setIsVoteLoading(false);
|
||||
}
|
||||
}, [post, voteResult, isVoteLoading]);
|
||||
|
||||
// 取消投票处理函数
|
||||
const handleUnvote = useCallback(async () => {
|
||||
if (!post || !voteResult || isVoteLoading || !voteResult.voted_option_id) return;
|
||||
|
||||
const votedOptionId = voteResult.voted_option_id;
|
||||
|
||||
// 保存旧状态用于回滚
|
||||
const oldVoteResult = { ...voteResult };
|
||||
|
||||
// 乐观更新
|
||||
setVoteResult((prev: VoteResultDTO | null) => {
|
||||
if (!prev) return null;
|
||||
return {
|
||||
@@ -611,12 +601,10 @@ export const PostDetailScreen: React.FC = () => {
|
||||
try {
|
||||
const success = await voteService.unvote(post.id);
|
||||
if (!success) {
|
||||
// 失败时回滚
|
||||
setVoteResult(oldVoteResult);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('取消投票失败:', error);
|
||||
// 失败时回滚
|
||||
handleError(error, { context: '取消投票' });
|
||||
setVoteResult(oldVoteResult);
|
||||
} finally {
|
||||
setIsVoteLoading(false);
|
||||
@@ -899,26 +887,15 @@ export const PostDetailScreen: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 点赞评论(包括回复)- 优化版
|
||||
const handleLikeComment = async (comment: Comment) => {
|
||||
const { id, is_liked, likes_count } = comment;
|
||||
|
||||
// 乐观更新:切换点赞状态
|
||||
const newIsLiked = !is_liked;
|
||||
const oldIsLiked = is_liked ?? false;
|
||||
const newIsLiked = !oldIsLiked;
|
||||
const newLikesCount = newIsLiked ? likes_count + 1 : Math.max(0, likes_count - 1);
|
||||
const originalComments = comments;
|
||||
|
||||
// 递归更新评论树中的点赞状态
|
||||
const updateLikeInTree = (c: Comment): Comment => {
|
||||
if (c.id === id) {
|
||||
return { ...c, is_liked: newIsLiked, likes_count: newLikesCount };
|
||||
}
|
||||
if (c.replies?.length) {
|
||||
return { ...c, replies: c.replies.map(r => updateLikeInTree(r)) };
|
||||
}
|
||||
return c;
|
||||
};
|
||||
|
||||
setComments(prev => prev.map(c => updateLikeInTree(c)));
|
||||
setComments(prev => toggleCommentLikeInTree(prev, id, newIsLiked, newLikesCount));
|
||||
|
||||
try {
|
||||
const success = newIsLiked
|
||||
@@ -929,18 +906,8 @@ export const PostDetailScreen: React.FC = () => {
|
||||
throw new Error('点赞操作失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('评论点赞操作失败:', error);
|
||||
// 回滚状态
|
||||
const rollbackLikeInTree = (c: Comment): Comment => {
|
||||
if (c.id === id) {
|
||||
return { ...c, is_liked, likes_count };
|
||||
}
|
||||
if (c.replies?.length) {
|
||||
return { ...c, replies: c.replies.map(r => rollbackLikeInTree(r)) };
|
||||
}
|
||||
return c;
|
||||
};
|
||||
setComments(prev => prev.map(c => rollbackLikeInTree(c)));
|
||||
handleError(error, { context: '评论点赞' });
|
||||
setComments(toggleCommentLikeInTree(originalComments, id, oldIsLiked, likes_count));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user