feat(content): add rich content rendering with @mentions and vote segments
Some checks failed
Frontend CI / build-and-push-web (push) Failing after 8m30s
Frontend CI / ota-android (push) Successful in 10m43s
Frontend CI / build-android-apk (push) Successful in 1h24m38s

- Add PostContentRenderer component for rendering posts/comments with segments
- Add PostMentionInput component for @mention support in post/comment creation
- Add VoteSegmentData and vote segment type for unified post creation API
- Update post and comment services to support segments in API requests
- Fix auth service to clear tokens before login/register
- Improve database initialization with proper close/retry logic
- Add verification check before allowing post creation
- Add metro web shims for react-native-webrtc full package and requireNativeComponent
- Update build config with git hash based version numbers
This commit is contained in:
lafay
2026-04-23 22:29:58 +08:00
parent eb4e1c080d
commit b2c3d5e54e
23 changed files with 797 additions and 127 deletions

View File

@@ -1,4 +1,4 @@
/**
/**
* 首页 HomeScreen
* 威友 - 首页展示
* 支持列表和多列网格模式(响应式布局)
@@ -27,7 +27,7 @@ import { PagerView } from '../../components/common';
import { useAppColors, useResolvedColorScheme, spacing, borderRadius, shadows, type AppColors } from '../../theme';
import { Post } from '../../types';
import { useUserStore, useHomeTabBarVisibilityStore, useHomeTabPressStore } from '../../stores';
import { useCurrentUser } from '../../stores/auth';
import { useCurrentUser, useIsAuthenticated, useIsVerified, useVerificationStore } from '../../stores/auth';
import { channelService, postService } from '../../services';
import { PostCard, TabBar, SearchBar, ShareSheet } from '../../components/business';
import type { PostCardAction } from '../../components/business/PostCard';
@@ -169,6 +169,10 @@ export const HomeScreen: React.FC = () => {
const insets = useSafeAreaInsets();
const { posts: storePosts } = useUserStore();
const currentUser = useCurrentUser();
const isAuthenticated = useIsAuthenticated();
const isVerified = useIsVerified();
const fetchVerificationStatus = useVerificationStore((s) => s.fetchStatus);
const hasFetchedVerification = useVerificationStore((s) => s.hasFetchedStatus);
const colors = useAppColors();
const resolvedScheme = useResolvedColorScheme();
const styles = useMemo(() => createHomeStyles(colors), [colors]);
@@ -207,6 +211,12 @@ export const HomeScreen: React.FC = () => {
const [showShareSheet, setShowShareSheet] = useState(false);
const [sharePost, setSharePost] = useState<Post | null>(null);
useEffect(() => {
if (isAuthenticated && !hasFetchedVerification) {
fetchVerificationStatus();
}
}, [isAuthenticated, hasFetchedVerification, fetchVerificationStatus]);
useEffect(() => {
if (showCreatePost) {
blurActiveElement();
@@ -656,6 +666,14 @@ export const HomeScreen: React.FC = () => {
// 跳转到发帖页面(使用 Modal 方式)
const handleCreatePost = () => {
if (!isAuthenticated) {
router.push('/login');
return;
}
if (!isVerified) {
router.push(hrefs.hrefVerificationGuide());
return;
}
setShowCreatePost(true);
};

View File

@@ -1,4 +1,4 @@
/**
/**
* 帖子详情页 PostDetailScreen - QQ频道风格响应式版本
* 威友 - 显示完整帖子内容和评论
* 桌面端使用双栏布局,移动端使用单栏布局
@@ -33,13 +33,13 @@ import {
useAppColors,
type AppColors,
} from '../../theme';
import { Post, Comment, VoteResultDTO, VoteOptionDTO } from '../../types';
import { Post, Comment, VoteResultDTO, VoteOptionDTO, MessageSegment } from '../../types';
import { useUserStore } from '../../stores';
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, ShareSheet } from '../../components/business';
import { CommentItem, VoteCard, ReportDialog, ShareSheet, PostContentRenderer, PostMentionInput } 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';
@@ -191,6 +191,7 @@ export const PostDetailScreen: React.FC = () => {
}
}, [commentsError, postId]);
const [commentText, setCommentText] = useState('');
const [commentSegments, setCommentSegments] = useState<MessageSegment[]>([]);
const [showImageModal, setShowImageModal] = useState(false);
const [selectedImageIndex, setSelectedImageIndex] = useState(0);
const [allImages, setAllImages] = useState<ImageGridItem[]>([]);
@@ -741,6 +742,7 @@ export const PostDetailScreen: React.FC = () => {
root_id: rootId,
target_id: parentId,
content: commentText.trim(),
segments: commentSegments.length > 0 ? commentSegments : undefined,
images: uploadedImageUrls.map(url => ({ url })),
likes_count: 0,
replies_count: 0,
@@ -798,6 +800,7 @@ export const PostDetailScreen: React.FC = () => {
const createdComment = await commentService.createComment({
postId: post.id,
content: commentText.trim(),
segments: commentSegments.length > 0 ? commentSegments : undefined,
parentId: parentId || undefined,
images: uploadedImageUrls,
});
@@ -1097,18 +1100,17 @@ export const PostDetailScreen: React.FC = () => {
{/* 内容 - 增大字体 */}
<View style={styles.contentContainer}>
<Text
variant="body"
style={[
<PostContentRenderer
content={post.content}
segments={post.segments}
textStyle={[
styles.content,
{
fontSize: isDesktop ? fontSizes.xl : isTablet ? fontSizes.lg : fontSizes.lg,
lineHeight: isDesktop ? 32 : isTablet ? 30 : 28
}
]}
>
{post.content}
</Text>
/>
</View>
{/* 图片 - 详情页显示所有图片,不限制数量 */}
@@ -1442,15 +1444,14 @@ export const PostDetailScreen: React.FC = () => {
)}
{/* Text Input */}
<TextInput
style={styles.expandedTextInput}
placeholder="来说点什么吧!"
placeholderTextColor={colors.text.hint}
<PostMentionInput
value={commentText}
onChangeText={setCommentText}
multiline
autoFocus
onSegmentsChange={setCommentSegments}
placeholder="来说点什么吧!"
maxLength={500}
minHeight={40}
style={styles.expandedTextInput}
/>
{/* Image preview */}