You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
3.8 KiB
172 lines
3.8 KiB
|
1 month ago
|
/**
|
||
|
|
* 公告栏组件
|
||
|
|
*
|
||
|
|
* 展示滚动公告,使用真实数据
|
||
|
|
*/
|
||
|
|
|
||
|
|
import React, { useState, useEffect, useRef } from 'react';
|
||
|
|
import {
|
||
|
|
View,
|
||
|
|
Text,
|
||
|
|
StyleSheet,
|
||
|
|
Animated,
|
||
|
|
TouchableOpacity,
|
||
|
|
Dimensions,
|
||
|
|
} from 'react-native';
|
||
|
|
import { createThemeStyles } from '@/theme';
|
||
|
|
import Colors from '@/constants/Colors';
|
||
|
|
import { mockNotices } from '@/services/mockHomeService';
|
||
|
|
import type { Notice } from '@/types/home';
|
||
|
|
|
||
|
|
const { width } = Dimensions.get('window');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 创建主题样式
|
||
|
|
*/
|
||
|
|
const styles = createThemeStyles((colors) => ({
|
||
|
|
container: {
|
||
|
|
backgroundColor: colors.backgroundSecondary,
|
||
|
|
paddingVertical: 10,
|
||
|
|
paddingHorizontal: 12,
|
||
|
|
marginHorizontal: 12,
|
||
|
|
marginBottom: 12,
|
||
|
|
borderRadius: 8,
|
||
|
|
flexDirection: 'row',
|
||
|
|
alignItems: 'center',
|
||
|
|
elevation: 2,
|
||
|
|
shadowColor: colors.cardShadow,
|
||
|
|
shadowOffset: { width: 0, height: 1 },
|
||
|
|
shadowOpacity: 0.1,
|
||
|
|
shadowRadius: 3,
|
||
|
|
},
|
||
|
|
label: {
|
||
|
|
fontSize: 12,
|
||
|
|
fontWeight: 'bold',
|
||
|
|
color: '#FFFFFF',
|
||
|
|
marginRight: 8,
|
||
|
|
backgroundColor: colors.primary,
|
||
|
|
paddingHorizontal: 8,
|
||
|
|
paddingVertical: 4,
|
||
|
|
borderRadius: 4,
|
||
|
|
},
|
||
|
|
content: {
|
||
|
|
flex: 1,
|
||
|
|
fontSize: 12,
|
||
|
|
color: colors.text,
|
||
|
|
overflow: 'hidden',
|
||
|
|
},
|
||
|
|
closeButton: {
|
||
|
|
marginLeft: 8,
|
||
|
|
padding: 4,
|
||
|
|
},
|
||
|
|
closeText: {
|
||
|
|
fontSize: 16,
|
||
|
|
color: colors.textSecondary,
|
||
|
|
},
|
||
|
|
}));
|
||
|
|
|
||
|
|
interface NoticeBarProps {
|
||
|
|
theme: 'light' | 'dark';
|
||
|
|
notices?: Notice[];
|
||
|
|
onNoticePress?: (notice: Notice) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 公告栏组件
|
||
|
|
*/
|
||
|
|
export default function NoticeBar({ theme, notices: propNotices, onNoticePress }: NoticeBarProps) {
|
||
|
|
const s = styles[theme];
|
||
|
|
const [notices, setNotices] = useState<Notice[]>(propNotices || []);
|
||
|
|
const [currentNotice, setCurrentNotice] = useState(0);
|
||
|
|
const [visible, setVisible] = useState(true);
|
||
|
|
const animatedValue = useRef(new Animated.Value(1)).current;
|
||
|
|
|
||
|
|
// 加载公告数据
|
||
|
|
useEffect(() => {
|
||
|
|
if (propNotices && propNotices.length > 0) {
|
||
|
|
setNotices(propNotices);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const loadNotices = async () => {
|
||
|
|
try {
|
||
|
|
// const data = await getNotices();
|
||
|
|
// setNotices(data.length > 0 ? data : mockNotices);
|
||
|
|
} catch (error) {
|
||
|
|
console.error('加载公告失败:', error);
|
||
|
|
setNotices(mockNotices);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
loadNotices();
|
||
|
|
}, [propNotices]);
|
||
|
|
|
||
|
|
// 自动切换公告
|
||
|
|
useEffect(() => {
|
||
|
|
if (notices.length === 0) return;
|
||
|
|
|
||
|
|
const timer = setInterval(() => {
|
||
|
|
setCurrentNotice((prev) => (prev + 1) % notices.length);
|
||
|
|
}, 5000);
|
||
|
|
|
||
|
|
return () => clearInterval(timer);
|
||
|
|
}, [notices.length]);
|
||
|
|
|
||
|
|
// 处理关闭公告
|
||
|
|
const handleClose = () => {
|
||
|
|
Animated.timing(animatedValue, {
|
||
|
|
toValue: 0,
|
||
|
|
duration: 300,
|
||
|
|
useNativeDriver: true,
|
||
|
|
}).start(() => {
|
||
|
|
setVisible(false);
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
// 处理公告点击
|
||
|
|
const handleNoticePress = () => {
|
||
|
|
if (notices.length > 0) {
|
||
|
|
onNoticePress?.(notices[currentNotice]);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!visible || notices.length === 0) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const currentNoticeData = notices[currentNotice];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Animated.View
|
||
|
|
style={[
|
||
|
|
s.container,
|
||
|
|
{
|
||
|
|
opacity: animatedValue,
|
||
|
|
transform: [
|
||
|
|
{
|
||
|
|
scaleY: animatedValue,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
]}
|
||
|
|
>
|
||
|
|
<Text style={s.label}>📢</Text>
|
||
|
|
<TouchableOpacity
|
||
|
|
style={{ flex: 1 }}
|
||
|
|
onPress={handleNoticePress}
|
||
|
|
activeOpacity={0.7}
|
||
|
|
>
|
||
|
|
<Text style={s.content} numberOfLines={1}>
|
||
|
|
{currentNoticeData.title || currentNoticeData.content}
|
||
|
|
</Text>
|
||
|
|
</TouchableOpacity>
|
||
|
|
<TouchableOpacity
|
||
|
|
style={s.closeButton}
|
||
|
|
onPress={handleClose}
|
||
|
|
>
|
||
|
|
<Text style={s.closeText}>✕</Text>
|
||
|
|
</TouchableOpacity>
|
||
|
|
</Animated.View>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|