feat(ui): add ShareSheet component for multi-channel post sharing
Some checks failed
Frontend CI / ota-android (push) Successful in 10m39s
Frontend CI / build-and-push-web (push) Failing after 1m29s
Frontend CI / build-android-apk (push) Has been cancelled

Add ShareSheet component that provides native sharing options (WeChat, Moments, copy link, etc.) replacing the previous clipboard-only approach. Also change like icon from heart to thumb-up to better match the share functionality.
This commit is contained in:
lafay
2026-04-19 23:58:26 +08:00
parent 6910fdec70
commit b16759a147
8 changed files with 148 additions and 60 deletions

View File

@@ -39,7 +39,7 @@ import { useCurrentUser } from '../../stores/auth';
import { postService, commentService, uploadService, authService, showPrompt, voteService } from '../../services';
import { postSyncService } from '@/services/post';
import { useCursorPagination } from '../../hooks/useCursorPagination';
import { CommentItem, VoteCard, ReportDialog } from '../../components/business';
import { CommentItem, VoteCard, ReportDialog, ShareSheet } 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/core';
@@ -207,6 +207,9 @@ export const PostDetailScreen: React.FC = () => {
// 举报相关状态
const [reportDialogVisible, setReportDialogVisible] = useState(false);
const [reportTarget, setReportTarget] = useState<{ type: 'post' | 'comment'; id: string } | null>(null);
// 分享面板状态
const [shareSheetVisible, setShareSheetVisible] = useState(false);
// 投票相关状态
const [voteResult, setVoteResult] = useState<VoteResultDTO | null>(null);
@@ -515,25 +518,10 @@ export const PostDetailScreen: React.FC = () => {
}
}, [post]);
// 分享帖子
// 分享帖子 - 打开分享面板
const handleShare = useCallback(async () => {
if (!post?.id) return;
try {
const res = await postService.sharePost(post.id, { channel: 'copy_link' });
if (res.ok && res.shares_count != null) {
setPost((prev) =>
prev
? { ...prev, shares_count: res.shares_count!, sharesCount: res.shares_count! }
: null
);
postSyncService.applyShareCountUpdate(post.id, res.shares_count);
}
} catch (error) {
console.error('上报分享次数失败:', error);
}
const postUrl = `https://browser.littlelan.cn/post/${encodeURIComponent(post.id)}`;
Clipboard.setString(postUrl);
Alert.alert('已复制', '帖子链接已复制到剪贴板');
setShareSheetVisible(true);
}, [post?.id]);
// 投票处理函数
@@ -1757,6 +1745,35 @@ export const PostDetailScreen: React.FC = () => {
setReportTarget(null);
}}
/>
{/* 分享面板 */}
<ShareSheet
visible={shareSheetVisible}
postUrl={post ? `https://browser.littlelan.cn/post/${encodeURIComponent(post.id)}` : undefined}
postTitle={post?.title}
onClose={() => {
setShareSheetVisible(false);
}}
onShareAction={(actionKey) => {
if (post) {
(async () => {
try {
const res = await postService.sharePost(post.id, { channel: actionKey });
if (res.ok && res.shares_count != null) {
setPost((prev) =>
prev
? { ...prev, shares_count: res.shares_count!, sharesCount: res.shares_count! }
: null
);
postSyncService.applyShareCountUpdate(post.id, res.shares_count!);
}
} catch (error) {
console.error('上报分享次数失败:', error);
}
})();
}
}}
/>
</View>
);
};
@@ -1982,18 +1999,14 @@ function createPostDetailStyles(colors: AppColors) {
commentSectionCount: {
marginLeft: spacing.xs,
fontSize: fontSizes.sm,
color: colors.text.hint,
},
commentSectionUnderline: {
width: 28,
height: 3,
backgroundColor: colors.text.primary,
marginTop: spacing.xs,
borderRadius: borderRadius.xs,
},
commentSectionCount: {
marginLeft: spacing.xs,
fontSize: fontSizes.sm,
color: colors.text.hint,
borderRadius: borderRadius.sm,
},
inputContainer: {
backgroundColor: colors.background.paper,
@@ -2179,7 +2192,7 @@ function createPostDetailStyles(colors: AppColors) {
height: 20,
backgroundColor: colors.background.disabled,
margin: 2,
borderRadius: borderRadius.xs,
borderRadius: borderRadius.sm,
},
emptyCommentsGridItemActive: {
backgroundColor: colors.text.hint,