feat: 首页更新
This commit is contained in:
239
pages/HomeScreen/components/NoticeBar/index.tsx
Normal file
239
pages/HomeScreen/components/NoticeBar/index.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
/**
|
||||
* 公告栏组件 - 增强版
|
||||
*
|
||||
* 基于 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user