147 lines
3.1 KiB
TypeScript
147 lines
3.1 KiB
TypeScript
|
|
/**
|
|||
|
|
* Avatar 头像组件
|
|||
|
|
* 支持图片URL、本地图片、首字母显示、在线状态徽章
|
|||
|
|
* 使用 expo-image 实现内存+磁盘双级缓存,头像秒加载
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import React from 'react';
|
|||
|
|
import {
|
|||
|
|
View,
|
|||
|
|
TouchableOpacity,
|
|||
|
|
StyleSheet,
|
|||
|
|
ViewStyle,
|
|||
|
|
} from 'react-native';
|
|||
|
|
import { Image as ExpoImage } from 'expo-image';
|
|||
|
|
import { colors, borderRadius } from '../../theme';
|
|||
|
|
import Text from './Text';
|
|||
|
|
|
|||
|
|
type AvatarSource = string | { uri: string } | number | null;
|
|||
|
|
|
|||
|
|
interface AvatarProps {
|
|||
|
|
source?: AvatarSource;
|
|||
|
|
size?: number; // 默认40
|
|||
|
|
name?: string; // 用于显示首字母
|
|||
|
|
onPress?: () => void;
|
|||
|
|
showBadge?: boolean;
|
|||
|
|
badgeColor?: string;
|
|||
|
|
style?: ViewStyle;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const Avatar: React.FC<AvatarProps> = ({
|
|||
|
|
source,
|
|||
|
|
size = 40,
|
|||
|
|
name,
|
|||
|
|
onPress,
|
|||
|
|
showBadge = false,
|
|||
|
|
badgeColor = colors.success.main,
|
|||
|
|
style,
|
|||
|
|
}) => {
|
|||
|
|
// 获取首字母
|
|||
|
|
const getInitial = (): string => {
|
|||
|
|
if (!name) return '?';
|
|||
|
|
const firstChar = name.charAt(0).toUpperCase();
|
|||
|
|
// 中文字符
|
|||
|
|
if (/[\u4e00-\u9fa5]/.test(firstChar)) {
|
|||
|
|
return firstChar;
|
|||
|
|
}
|
|||
|
|
return firstChar;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 渲染头像内容
|
|||
|
|
const renderAvatarContent = () => {
|
|||
|
|
// 如果有图片源
|
|||
|
|
if (source) {
|
|||
|
|
const imageSource =
|
|||
|
|
typeof source === 'string' ? { uri: source } : source;
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<ExpoImage
|
|||
|
|
source={imageSource}
|
|||
|
|
style={[
|
|||
|
|
styles.image,
|
|||
|
|
{
|
|||
|
|
width: size,
|
|||
|
|
height: size,
|
|||
|
|
borderRadius: size / 2,
|
|||
|
|
},
|
|||
|
|
]}
|
|||
|
|
contentFit="cover"
|
|||
|
|
cachePolicy="memory"
|
|||
|
|
transition={150}
|
|||
|
|
/>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示首字母
|
|||
|
|
const fontSize = size / 2;
|
|||
|
|
return (
|
|||
|
|
<View
|
|||
|
|
style={[
|
|||
|
|
styles.placeholder,
|
|||
|
|
{
|
|||
|
|
width: size,
|
|||
|
|
height: size,
|
|||
|
|
borderRadius: size / 2,
|
|||
|
|
},
|
|||
|
|
]}
|
|||
|
|
>
|
|||
|
|
<Text color={colors.text.inverse} style={{ fontSize, lineHeight: fontSize * 1.2 }}>
|
|||
|
|
{getInitial()}
|
|||
|
|
</Text>
|
|||
|
|
</View>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const avatarContainer = (
|
|||
|
|
<View style={[styles.container, style]}>
|
|||
|
|
{renderAvatarContent()}
|
|||
|
|
{showBadge && (
|
|||
|
|
<View
|
|||
|
|
style={[
|
|||
|
|
styles.badge,
|
|||
|
|
{
|
|||
|
|
backgroundColor: badgeColor,
|
|||
|
|
width: size * 0.3,
|
|||
|
|
height: size * 0.3,
|
|||
|
|
borderRadius: (size * 0.3) / 2,
|
|||
|
|
right: 0,
|
|||
|
|
bottom: 0,
|
|||
|
|
},
|
|||
|
|
]}
|
|||
|
|
/>
|
|||
|
|
)}
|
|||
|
|
</View>
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (onPress) {
|
|||
|
|
return (
|
|||
|
|
<TouchableOpacity onPress={onPress} activeOpacity={0.7}>
|
|||
|
|
{avatarContainer}
|
|||
|
|
</TouchableOpacity>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return avatarContainer;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const styles = StyleSheet.create({
|
|||
|
|
container: {
|
|||
|
|
position: 'relative',
|
|||
|
|
},
|
|||
|
|
image: {
|
|||
|
|
backgroundColor: colors.background.disabled,
|
|||
|
|
},
|
|||
|
|
placeholder: {
|
|||
|
|
backgroundColor: colors.primary.main,
|
|||
|
|
justifyContent: 'center',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
},
|
|||
|
|
badge: {
|
|||
|
|
position: 'absolute',
|
|||
|
|
borderWidth: 2,
|
|||
|
|
borderColor: colors.background.paper,
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
export default Avatar;
|