Files
rn-app/pages/HomeScreen/components/NoticeBar/index.tsx

240 lines
7.6 KiB
TypeScript
Raw Normal View History

2025-11-13 16:47:10 +08:00
/**
* -
*
* xinyong-web NoticeBar
*
* -
* -
* -
* - light/dark/orange
*/
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import {
View,
Text,
StyleSheet,
Animated,
TouchableOpacity,
Dimensions,
ScrollView,
Modal,
Linking,
Image as RNImage,
ActivityIndicator,
} from 'react-native';
// import { Button } from 'react-native-paper';
import { useThemeColors, useColorScheme } from '@/theme';
// import { mockNotices } from '@/services/mockHomeService';
// import type { Notice } from '@/types/home';
import useMsgStore, { useUnreadMessageTotal } from '@/stores/msgStore';
import { filter, map } from 'lodash-es';
import { styles } from './styles';
import { Ionicons } from '@expo/vector-icons';
interface NoticeBarProps {
onNoticePress?: (notice: Record<string, any>) => void;
onMessagePress?: () => void;
onServicePress?: () => void;
unreadCount?: number;
}
/**
* -
*/
export default function NoticeBar({
onNoticePress,
onMessagePress,
onServicePress,
unreadCount = 0,
}: NoticeBarProps) {
const colorScheme = useColorScheme();
const s = styles[colorScheme];
const themeColors = useThemeColors();
const [currentNotice, setCurrentNotice] = useState(0);
const [visible, setVisible] = useState(true);
const [showDetailModal, setShowDetailModal] = useState(false);
const [imageLoading, setImageLoading] = useState(true);
const animatedValue = useRef(new Animated.Value(1)).current;
const { notices } = useMsgStore();
const unreadMessageTotal = useUnreadMessageTotal();
const noticeList = useMemo(() => {
return map(
filter(notices, (item) => item.message_id == 13 && !!item.content),
(notice) => {
return {
...notice,
content: notice.content.replace(
/<(\/?)(p|span|div|br|strong|em|b|i|u|s|ul|li|ol|h1|h2|h3|h4|h5|h6|a|img|table|tr|td|th|tbody|thead|tfoot|style|script)[^>]*>/gi,
''
),
};
}
);
}, [notices]);
// 自动切换公告
useEffect(() => {
if (noticeList.length === 0) return;
const timer = setInterval(() => {
setCurrentNotice((prev) => (prev + 1) % noticeList.length);
}, 5000);
return () => clearInterval(timer);
}, [noticeList.length]);
// 处理关闭公告
const handleClose = useCallback(() => {
Animated.timing(animatedValue, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start(() => {
setVisible(false);
});
}, [animatedValue]);
// 处理公告点击 - 打开详情弹窗
const handleNoticePress = useCallback(() => {
if (noticeList.length > 0) {
setShowDetailModal(true);
onNoticePress?.(noticeList[currentNotice]);
}
}, [noticeList, currentNotice, onNoticePress]);
// 处理链接点击
const handleLinkPress = useCallback((url: string) => {
Linking.openURL(url).catch((err) => {
console.error('打开链接失败:', err);
});
}, []);
if (!visible || noticeList.length === 0) {
return null;
}
const currentNoticeData = noticeList[currentNotice] as Record<string, any>;
return (
<>
{/* 顶部通知栏 */}
<Animated.View
style={[
s.container,
{
opacity: animatedValue,
transform: [
{
scaleY: animatedValue,
},
],
},
]}
>
<Ionicons
name="volume-medium-outline"
size={24}
color={themeColors.tint}
style={s.volumeIcon}
/>
<TouchableOpacity style={{ flex: 1 }} onPress={handleNoticePress} activeOpacity={0.7}>
<Text style={s.content} numberOfLines={1}>
{currentNoticeData.content}
</Text>
</TouchableOpacity>
{/* 右侧按钮组 */}
<View style={s.rightButtonsContainer}>
<TouchableOpacity style={s.actionButton} onPress={onMessagePress} activeOpacity={0.7}>
<Ionicons name="volume-medium-outline" size={18} color={themeColors.tint} />
{/*{unreadCount > 0 && (*/}
{/* <Text style={[s.actionButtonText, { marginLeft: 4 }]}>{unreadCount}</Text>*/}
{/*)}*/}
<Text style={[s.actionButtonText, { marginLeft: 4 }]}></Text>
</TouchableOpacity>
<TouchableOpacity style={s.actionButton} onPress={onServicePress} activeOpacity={0.7}>
<Ionicons name="headset" size={18} color={themeColors.tint} />
<Text style={s.actionButtonText}></Text>
</TouchableOpacity>
{/*<TouchableOpacity style={s.closeButton} onPress={handleClose}>*/}
{/* <Text style={s.closeText}>✕</Text>*/}
{/*</TouchableOpacity>*/}
</View>
</Animated.View>
{/* 公告详情弹窗 */}
<Modal
visible={showDetailModal}
transparent
animationType="fade"
onRequestClose={() => setShowDetailModal(false)}
>
<View style={s.modalOverlay}>
<View style={s.modalContent}>
{/* 弹窗头部 */}
<View style={s.modalHeader}>
<Text style={s.modalTitle}></Text>
<TouchableOpacity
style={s.modalCloseButton}
onPress={() => setShowDetailModal(false)}
>
<Text style={s.modalCloseText}></Text>
</TouchableOpacity>
</View>
{/* 弹窗内容 */}
<ScrollView style={s.modalBody}>
{currentNoticeData.title && (
<Text style={s.noticeTitle}>{currentNoticeData.title}</Text>
)}
{currentNoticeData.create_time && (
<Text style={s.noticeDate}>
{currentNoticeData.formatDate || currentNoticeData.create_time}
</Text>
)}
{/* 根据内容类型渲染不同的内容 */}
{currentNoticeData.content_type === 1 && (
<Text style={s.noticeTextContent}>{currentNoticeData.content}</Text>
)}
{currentNoticeData.content_type === 2 && currentNoticeData.content && (
<View style={s.noticeImageContainer}>
{imageLoading && (
<ActivityIndicator
size="large"
color={themeColors.primary}
style={s.imageLoader}
/>
)}
<RNImage
source={{ uri: currentNoticeData.content }}
style={s.noticeImage}
resizeMode="contain"
onLoadStart={() => {
setImageLoading(true);
}}
onLoadEnd={() => {
setImageLoading(false);
}}
onError={(error) => {
console.error('图片加载失败:', error);
setImageLoading(false);
}}
/>
</View>
)}
{currentNoticeData.content_type === 3 && (
<TouchableOpacity onPress={() => handleLinkPress(currentNoticeData.content)}>
<Text style={s.noticeLink}>{currentNoticeData.content}</Text>
</TouchableOpacity>
)}
</ScrollView>
</View>
</View>
</Modal>
</>
);
}