Migrate frontend realtime messaging to SSE.

Switch service integrations and screen/store consumers from websocket events to SSE, and ignore generated dist-web artifacts.

Made-with: Cursor
This commit is contained in:
2026-03-10 12:58:23 +08:00
parent 63e32b15a3
commit be84c01abd
25 changed files with 974 additions and 1305 deletions

View File

@@ -21,7 +21,7 @@ import {
Image,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useNavigation } from '@react-navigation/native';
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import * as ImagePicker from 'expo-image-picker';
import { colors, spacing, fontSizes, borderRadius, shadows } from '../../theme';
@@ -31,6 +31,7 @@ import { ApiError } from '../../services/api';
import { uploadService } from '../../services/uploadService';
import VoteEditor from '../../components/business/VoteEditor';
import { useResponsive, useResponsiveValue } from '../../hooks';
import { RootStackParamList } from '../../navigation/types';
const MAX_TITLE_LENGTH = 100;
const MAX_CONTENT_LENGTH = 2000;
@@ -57,9 +58,9 @@ const EMOJIS = [
'👍', '👎', '✊', '👊', '🤛', '🤜', '👏', '🙌',
'👐', '🤲', '🤝', '🙏', '✍️', '💪', '🦾', '🦵',
'❤️', '🧡', '💛', '💚', '💙', '💜', '🖤', '🤍',
'🤎', '💔', '❤️\u200d🔥', '❤️\u200d🩹', '💕', '💞', '💓', '💗',
'💖', '💘', '💝', '🎉', '🎊', '🎁', '🎈', '✨',
'🔥', '💯', '💢', '💥', '💫', '💦', '💨', '🕳️',
'🤎', '💔', '🩹', '💕', '💞', '💓', '💗', '💖',
'💘', '💝', '🎉', '🎊', '🎁', '🎈', '✨', '🔥',
'💯', '💢', '💥', '💫', '💦', '💨', '🕳️',
];
// 动画值
@@ -74,6 +75,9 @@ const getPublishErrorMessage = (error: unknown): string => {
export const CreatePostScreen: React.FC = () => {
const navigation = useNavigation();
const route = useRoute<RouteProp<RootStackParamList, 'CreatePost'>>();
const isEditMode = route.params?.mode === 'edit' && !!route.params?.postId;
const editPostID = route.params?.postId || '';
// 响应式布局
const { isWideScreen, width } = useResponsive();
@@ -86,6 +90,7 @@ export const CreatePostScreen: React.FC = () => {
const [showTagInput, setShowTagInput] = useState(false);
const [showEmojiPanel, setShowEmojiPanel] = useState(false);
const [posting, setPosting] = useState(false);
const [loadingPost, setLoadingPost] = useState(false);
// 投票相关状态
const [isVotePost, setIsVotePost] = useState(false);
@@ -122,6 +127,41 @@ export const CreatePostScreen: React.FC = () => {
]).start();
}, []);
React.useLayoutEffect(() => {
navigation.setOptions({
title: isEditMode ? '编辑帖子' : '发布帖子',
});
}, [navigation, isEditMode]);
React.useEffect(() => {
if (!isEditMode || !editPostID) {
return;
}
const loadPostForEdit = async () => {
setLoadingPost(true);
try {
const existingPost = await postService.getPost(editPostID);
if (!existingPost) {
Alert.alert('提示', '帖子不存在或已被删除');
navigation.goBack();
return;
}
setTitle(existingPost.title || '');
setContent(existingPost.content || '');
setImages((existingPost.images || []).map((img) => ({ uri: img.url, uploading: false })));
} catch (error) {
console.error('加载待编辑帖子失败:', error);
Alert.alert('错误', '加载帖子失败,请稍后重试');
navigation.goBack();
} finally {
setLoadingPost(false);
}
};
loadPostForEdit();
}, [isEditMode, editPostID, navigation]);
// 选择图片
const handlePickImage = async () => {
const permissionResult = await ImagePicker.requestMediaLibraryPermissionsAsync();
@@ -323,19 +363,37 @@ export const CreatePostScreen: React.FC = () => {
});
navigation.goBack();
} else {
// 创建普通帖子
await postService.createPost({
title: title.trim() || '无标题',
content: content.trim(),
images: imageUrls,
});
showPrompt({
type: 'info',
title: '审核中',
message: '帖子已提交,内容审核中,稍后展示',
duration: 2600,
});
navigation.goBack();
if (isEditMode && editPostID) {
const updated = await postService.updatePost(editPostID, {
title: title.trim() || '无标题',
content: content.trim(),
images: imageUrls,
});
if (!updated) {
throw new Error('更新帖子失败');
}
showPrompt({
type: 'success',
title: '修改成功',
message: '帖子内容已更新',
duration: 2200,
});
navigation.goBack();
} else {
// 创建普通帖子
await postService.createPost({
title: title.trim() || '无标题',
content: content.trim(),
images: imageUrls,
});
showPrompt({
type: 'info',
title: '审核中',
message: '帖子已提交,内容审核中,稍后展示',
duration: 2600,
});
navigation.goBack();
}
}
} catch (error) {
console.error('发布帖子失败:', error);
@@ -631,7 +689,7 @@ export const CreatePostScreen: React.FC = () => {
<MaterialCommunityIcons name="loading" size={18} color={colors.primary.contrast} />
) : (
<Text variant="body" color={colors.primary.contrast} style={styles.postButtonText}>
{isEditMode ? '保存' : '发布'}
</Text>
)}
</TouchableOpacity>
@@ -677,10 +735,24 @@ export const CreatePostScreen: React.FC = () => {
>
{isWideScreen ? (
<ResponsiveContainer maxWidth={800}>
{renderMainContent()}
{loadingPost ? (
<View style={styles.loadingContainer}>
<MaterialCommunityIcons name="loading" size={28} color={colors.primary.main} />
<Text variant="body" color={colors.text.secondary} style={styles.loadingText}>...</Text>
</View>
) : (
renderMainContent()
)}
</ResponsiveContainer>
) : (
renderMainContent()
loadingPost ? (
<View style={styles.loadingContainer}>
<MaterialCommunityIcons name="loading" size={28} color={colors.primary.main} />
<Text variant="body" color={colors.text.secondary} style={styles.loadingText}>...</Text>
</View>
) : (
renderMainContent()
)
)}
</KeyboardAvoidingView>
</SafeAreaView>
@@ -934,6 +1006,15 @@ const styles = StyleSheet.create({
emojiText: {
fontSize: 24,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
gap: spacing.md,
},
loadingText: {
fontSize: fontSizes.md,
},
});
export default CreatePostScreen;