Files
2025-11-13 16:47:10 +08:00

240 lines
7.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 公告栏组件 - 增强版
*
* 基于 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>
</>
);
}