240 lines
7.6 KiB
TypeScript
240 lines
7.6 KiB
TypeScript
/**
|
||
* 公告栏组件 - 增强版
|
||
*
|
||
* 基于 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>
|
||
</>
|
||
);
|
||
}
|