rn_for_openharmony商城项目app实战-消息列表实现
摘要 本文实现了一个App消息中心功能,包含消息列表展示、已读/未读状态管理、删除和查看详情等操作。消息数据分为系统通知、订单消息和促销活动三种类型,通过配置对象管理不同类型对应的图标和标签。使用FlatList渲染消息列表,每条消息卡片显示标题、时间、类型标签和内容预览,未读消息有视觉标记。支持点击查看详情、长按删除、全部已读等功能。全局状态管理消息数据及相关操作,如标记已读、删除等。整体设计注
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_buy
写在前面

消息中心是 App 和用户沟通的重要渠道。订单发货了、有新的促销活动、系统有重要通知,都可以通过消息推送给用户。
消息列表页要展示所有消息,区分已读和未读,支持查看详情、标记已读、删除等操作。这篇文章来实现这些功能。
消息数据结构
先看看消息数据长什么样:
interface Message {
id: number;
title: string; // 消息标题
content: string; // 消息内容
time: string; // 发送时间
read: boolean; // 是否已读
type: 'system' | 'order' | 'promotion'; // 消息类型
}
消息分三种类型:系统通知、订单消息、促销活动。不同类型显示不同的图标和标签。
类型配置
用配置对象管理不同类型的图标和标签:
const typeIcons: Record<string, string> = {
system: '📢',
order: '📦',
promotion: '🎁'
};
const typeLabels: Record<string, string> = {
system: '系统通知',
order: '订单消息',
promotion: '促销活动'
};
系统通知用喇叭图标,订单消息用包裹图标,促销活动用礼物图标。这样用户一眼就能看出是什么类型的消息。
为什么用配置对象?
如果在代码里写
if (type === 'system') return '📢',后面要加新类型或改图标都很麻烦。用配置对象的话,改一个地方就够了,而且代码更简洁:typeIcons[type]。
引入依赖
import React from 'react';
import {View, Text, FlatList, StyleSheet, TouchableOpacity, Alert} from 'react-native';
import {useApp} from '../store/AppContext';
import {Header} from '../components/Header';
import {Empty} from '../components/Empty';
import {Message} from '../types';
标准的列表页依赖,加上 Alert 用于删除确认。
组件主体
export const MessageListScreen = () => {
const {messages, navigate, markAllRead, deleteMessage, unreadCount} = useApp();
从全局状态拿消息列表和相关方法:
messages:消息列表markAllRead:标记全部已读deleteMessage:删除消息unreadCount:未读消息数量
删除处理
const handleDelete = (id: number) => {
Alert.alert('删除消息', '确定要删除这条消息吗?', [
{text: '取消', style: 'cancel'},
{text: '删除', style: 'destructive', onPress: () => deleteMessage(id)},
]);
};
删除需要二次确认,避免误操作。删除后消息就没了,不像已读还能恢复。
消息卡片渲染
const renderMessage = ({item}: {item: Message}) => (
<TouchableOpacity
style={styles.messageCard}
onPress={() => navigate('messageDetail', {message: item})}
onLongPress={() => handleDelete(item.id)}
>
点击进入消息详情,长按删除消息。长按删除是移动端常见的交互方式,不占用界面空间。
为什么用长按删除?
如果每条消息都显示删除按钮,界面会很乱。长按删除既能实现功能,又保持界面简洁。不过要在某个地方提示用户"长按可删除",不然用户可能不知道。
图标区域
<View style={styles.iconContainer}>
<View style={[styles.iconWrap, !item.read && styles.iconWrapUnread]}>
<Text style={styles.icon}>{typeIcons[item.type]}</Text>
</View>
{!item.read && <View style={styles.dot} />}
</View>
左边是消息类型图标,根据类型显示不同的 emoji。未读消息的图标背景是浅蓝色,已读的是灰色。
未读消息右上角有个红点,这是最常见的未读标记方式。
内容区域
<View style={styles.content}>
<View style={styles.titleRow}>
<Text style={[styles.title, !item.read && styles.unreadTitle]} numberOfLines={1}>
{item.title}
</Text>
<Text style={styles.time}>{item.time}</Text>
</View>
第一行是标题和时间。未读消息的标题加粗,视觉上更突出。标题限制一行,超出显示省略号。
<Text style={styles.typeLabel}>{typeLabels[item.type]}</Text>
<Text style={styles.preview} numberOfLines={2}>{item.content}</Text>
</View>
</TouchableOpacity>
);
第二行是消息类型标签,蓝色小字。第三行是内容预览,限制两行。用户看预览就能大概知道消息内容,决定要不要点进去看详情。
页面头部
return (
<View style={styles.container}>
<Header
title="消息中心"
rightElement={
unreadCount > 0 ? (
<TouchableOpacity onPress={markAllRead}>
<Text style={styles.markAllBtn}>全部已读</Text>
</TouchableOpacity>
) : undefined
}
/>
头部右边有个"全部已读"按钮,只在有未读消息时显示。点击把所有消息标记为已读。
全部已读要不要确认?
我这里没加确认。标记已读不是什么严重的操作,用户想看未读消息还是能看到内容的,只是红点没了。如果加确认反而显得啰嗦。
消息统计
{messages.length > 0 && (
<View style={styles.summary}>
<Text style={styles.summaryText}>共 {messages.length} 条消息</Text>
{unreadCount > 0 && <Text style={styles.unreadText}>{unreadCount} 条未读</Text>}
</View>
)}
列表上方显示消息总数和未读数。让用户对消息情况有个整体了解。
列表渲染
{messages.length === 0 ? (
<Empty icon="🔔" title="暂无消息" subtitle="您还没有收到任何消息" />
) : (
<FlatList
data={messages}
keyExtractor={item => item.id.toString()}
contentContainerStyle={styles.list}
renderItem={renderMessage}
/>
)}
</View>
);
};
没有消息时显示空状态,有消息时用 FlatList 渲染列表。
Context 里的消息操作
看看全局状态里消息相关的方法:
const [messages, setMessages] = useState<Message[]>(mockMessages);
初始化了一些模拟数据。
标记单条已读:
const markMessageRead = (id: number) => {
setMessages(prev => prev.map(m => (m.id === id ? {...m, read: true} : m)));
};
把指定消息的 read 设为 true。这个方法在消息详情页调用,用户打开详情就标记已读。
标记全部已读:
const markAllRead = () => {
setMessages(prev => prev.map(m => ({...m, read: true})));
};
把所有消息的 read 都设为 true。
删除消息:
const deleteMessage = (id: number) => {
setMessages(prev => prev.filter(m => m.id !== id));
};
用 filter 过滤掉指定消息。
计算未读数量:
const unreadCount = messages.filter(m => !m.read).length;
这是个派生状态,从 messages 实时计算出来。
样式详解
消息卡片的样式:
messageCard: {
flexDirection: 'row',
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
},
横向布局,左边图标右边内容。
未读红点的样式:
dot: {
position: 'absolute',
top: 0,
right: 0,
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: '#e74c3c',
borderWidth: 2,
borderColor: '#fff',
},
绝对定位在图标右上角,红色圆点。borderColor: '#fff' 加了白色边框,让红点和背景有个分隔,更醒目。
图标容器的样式:
iconWrap: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#f5f5f5',
justifyContent: 'center',
alignItems: 'center',
},
iconWrapUnread: {backgroundColor: '#e8f4fc'},
圆形容器,已读是灰色背景,未读是浅蓝色背景。
消息分组
当前所有消息混在一起,可以按类型或日期分组:
const groupByType = (messages: Message[]) => {
const groups: Record<string, Message[]> = {};
messages.forEach(m => {
if (!groups[m.type]) groups[m.type] = [];
groups[m.type].push(m);
});
return Object.entries(groups).map(([type, items]) => ({
title: typeLabels[type],
data: items,
}));
};
然后用 SectionList 渲染,每种类型一个 section。
滑动操作
可以加滑动操作,左滑显示"删除"和"标记已读"按钮:
import {Swipeable} from 'react-native-gesture-handler';
<Swipeable
renderRightActions={() => (
<View style={styles.swipeActions}>
<TouchableOpacity onPress={() => markMessageRead(item.id)}>
<Text>已读</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleDelete(item.id)}>
<Text>删除</Text>
</TouchableOpacity>
</View>
)}
>
{/* 消息卡片内容 */}
</Swipeable>
滑动操作比长按更直观,用户更容易发现。
消息推送
当前消息是写死的,正式项目应该接入推送服务:
- 用户打开 App 时,从服务器拉取消息列表
- App 在后台时,通过推送服务接收新消息
- 收到新消息时,更新本地消息列表,显示角标
推送服务可以用 Firebase Cloud Messaging、极光推送等。
写在最后
消息列表页的核心是已读/未读状态的管理和视觉区分。未读消息要有明显的标记(红点、加粗、不同背景色),让用户一眼就能看出哪些是新消息。
长按删除、全部已读这些操作要考虑用户体验,该确认的确认,不该确认的别啰嗦。
下一篇写消息详情页,展示消息的完整内容。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)