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>
|
|||
|
|
</>
|
|||
|
|
);
|
|||
|
|
}
|