【HarmonyOS】DAY22:React Native for OpenHarmony:Calendar 日程标记与事件管理
日程标记是日历组件的核心功能之一,它通过视觉提示让用户快速识别有特殊事件的日期。本文将详细讲解如何在 OpenHarmony 平台上实现高效的日程标记系统。
·
React Native for OpenHarmony:Calendar 日程标记与事件管理

🌟 感谢陪伴~ 小白博主在线求友
🌿 跟着小白学Linux/Java/Python
📖 专栏汇总:
《Linux》专栏 | 《Java》专栏 | 《Python》专栏

description: 深入解析在 OpenHarmony 平台上实现日历日程标记功能的技术方案,包含标记系统设计、事件管理、性能优化策略
tags:
- react-native
- openharmony
- calendar
- event-management
category: 移动开发
React Native for OpenHarmony:Calendar 日程标记与事件管理
概述
日程标记是日历组件的核心功能之一,它通过视觉提示让用户快速识别有特殊事件的日期。本文将详细讲解如何在 OpenHarmony 平台上实现高效的日程标记系统。
标记系统设计
数据结构设计
// types/event.ts
export enum EventType {
MEETING = 'meeting',
TASK = 'task',
REMINDER = 'reminder',
HOLIDAY = 'holiday',
}
export interface CalendarEvent {
id: string;
date: string; // ISO 8601 格式
title: string;
type: EventType;
color: string;
description?: string;
completed?: boolean;
}
export interface EventMarker {
events: CalendarEvent[];
hasOverflow: boolean;
displayCount: number;
}
事件管理器
// utils/eventManager.ts
import { CalendarEvent, EventType, EventMarker } from '../types/event';
export class EventManager {
private events: Map<string, CalendarEvent[]> = new Map();
private readonly MAX_DISPLAY_DOTS = 3;
/**
* 添加事件
*/
addEvent(event: CalendarEvent): void {
const date = event.date;
if (!this.events.has(date)) {
this.events.set(date, []);
}
this.events.get(date)!.push(event);
this.sortEventsByType(date);
}
/**
* 批量添加事件
*/
addEvents(events: CalendarEvent[]): void {
events.forEach(event => this.addEvent(event));
}
/**
* 获取指定日期的事件
*/
getEvents(date: string): CalendarEvent[] {
return this.events.get(date) || [];
}
/**
* 获取日期标记信息
*/
getMarker(date: string): EventMarker {
const events = this.getEvents(date);
const displayCount = Math.min(events.length, this.MAX_DISPLAY_DOTS);
return {
events: events.slice(0, this.MAX_DISPLAY_DOTS),
hasOverflow: events.length > this.MAX_DISPLAY_DOTS,
displayCount,
};
}
/**
* 删除事件
*/
removeEvent(eventId: string): boolean {
for (const [date, events] of this.events.entries()) {
const index = events.findIndex(e => e.id === eventId);
if (index !== -1) {
events.splice(index, 1);
if (events.length === 0) {
this.events.delete(date);
}
return true;
}
}
return false;
}
/**
* 获取有事件的日期列表
*/
getEventDates(): string[] {
return Array.from(this.events.keys()).sort();
}
/**
* 清空所有事件
*/
clear(): void {
this.events.clear();
}
/**
* 按类型排序事件(优先级高的在前)
*/
private sortEventsByType(date: string): void {
const events = this.events.get(date);
if (!events) return;
const typePriority: Record<EventType, number> = {
[EventType.HOLIDAY]: 0,
[EventType.MEETING]: 1,
[EventType.TASK]: 2,
[EventType.REMINDER]: 3,
};
events.sort((a, b) => typePriority[a.type] - typePriority[b.type]);
}
}
颜色配置
// constants/colors.ts
import { EventType } from '../types/event';
export const EVENT_COLORS: Record<EventType, string> = {
[EventType.MEETING]: '#9C27B0',
[EventType.TASK]: '#2196F3',
[EventType.REMINDER]: '#FF5722',
[EventType.HOLIDAY]: '#4CAF50',
};
export const EVENT_LABELS: Record<EventType, { zh: string; en: string }> = {
[EventType.MEETING]: { zh: '会议', en: 'Meeting' },
[EventType.TASK]: { zh: '任务', en: 'Task' },
[EventType.REMINDER]: { zh: '提醒', en: 'Reminder' },
[EventType.HOLIDAY]: { zh: '假期', en: 'Holiday' },
};
组件实现
日历组件
// components/EventCalendar.tsx
import React, { useState, useCallback, useMemo, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
TouchableOpacity,
Alert,
} from 'react-native';
import { CalendarEvent, EventType } from '../types/event';
import { EventManager } from '../utils/eventManager';
import { EVENT_COLORS, EVENT_LABELS } from '../constants/colors';
import { DateUtils } from '../utils/dateUtils';
interface EventCalendarProps {
initialEvents?: CalendarEvent[];
onEventPress?: (event: CalendarEvent) => void;
onDateSelect?: (date: string) => void;
}
export const EventCalendar: React.FC<EventCalendarProps> = ({
initialEvents = [],
onEventPress,
onDateSelect,
}) => {
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedDate, setSelectedDate] = useState<string>('');
const [eventManager] = useState(() => new EventManager());
// 初始化事件
useEffect(() => {
if (initialEvents.length > 0) {
eventManager.addEvents(initialEvents);
}
}, [initialEvents, eventManager]);
// 获取月份数据
const monthData = useMemo(() => {
return DateUtils.getMonthData(currentDate);
}, [currentDate]);
// 切换月份
const changeMonth = useCallback((offset: number) => {
setCurrentDate((prev) => {
const newDate = new Date(prev);
newDate.setMonth(newDate.getMonth() + offset);
return newDate;
});
}, []);
// 选择日期
const selectDate = useCallback((dateString: string) => {
setSelectedDate(dateString);
onDateSelect?.(dateString);
}, [onDateSelect]);
// 渲染标记点
const renderEventDots = useCallback((dateString: string) => {
const marker = eventManager.getMarker(dateString);
if (marker.displayCount === 0) return null;
return (
<View style={styles.dotsContainer}>
{marker.events.map((event, index) => (
<View
key={event.id}
style={[
styles.eventDot,
{ backgroundColor: event.color }
]}
/>
))}
{marker.hasOverflow && (
<View style={styles.moreDot}>
<Text style={styles.moreDotText}>+</Text>
</View>
)}
</View>
);
}, [eventManager]);
// 获取选中日期的事件
const selectedEvents = useMemo(() => {
return selectedDate ? eventManager.getEvents(selectedDate) : [];
}, [selectedDate, eventManager]);
// 渲染日期网格
const calendarGrid = useMemo(() => {
const rows: React.ReactNode[] = [];
for (let i = 0; i < 6; i++) {
const rowDays = monthData.days.slice(i * 7, (i + 1) * 7);
rows.push(
<View key={i} style={styles.weekRow}>
{rowDays.map((day, index) => {
const isSelected = selectedDate === day.dateString;
const hasEvents = eventManager.getMarker(day.dateString).displayCount > 0;
return (
<TouchableOpacity
key={index}
style={[
styles.dayCell,
!day.isCurrentMonth && styles.dayCellDisabled,
day.isToday && styles.dayCellToday,
isSelected && styles.dayCellSelected,
hasEvents && styles.dayCellWithEvents,
]}
onPress={() => day.isCurrentMonth && selectDate(day.dateString)}
disabled={!day.isCurrentMonth}
activeOpacity={0.7}
>
<Text
style={[
styles.dayText,
!day.isCurrentMonth && styles.dayTextDisabled,
day.isToday && styles.dayTextToday,
isSelected && styles.dayTextSelected,
]}
>
{day.day}
</Text>
{day.isCurrentMonth && renderEventDots(day.dateString)}
</TouchableOpacity>
);
})}
</View>
);
}
return rows;
}, [monthData.days, selectedDate, eventManager, selectDate, renderEventDots]);
return (
<ScrollView style={styles.container}>
{/* 日历卡片 */}
<View style={styles.calendarCard}>
{/* 月份导航 */}
<View style={styles.monthNavigation}>
<TouchableOpacity
style={styles.navButton}
onPress={() => changeMonth(-1)}
>
<Text style={styles.navButtonText}>‹</Text>
</TouchableOpacity>
<Text style={styles.monthText}>
{monthData.year}年 {DateUtils.getMonthName(monthData.month)}
</Text>
<TouchableOpacity
style={styles.navButton}
onPress={() => changeMonth(1)}
>
<Text style={styles.navButtonText}>›</Text>
</TouchableOpacity>
</View>
{/* 星期标题 */}
<View style={styles.weekHeader}>
{DateUtils.getWeekDays().map((day, index) => (
<View key={index} style={styles.weekDayCell}>
<Text style={styles.weekDayText}>{day}</Text>
</View>
))}
</View>
{/* 日期网格 */}
<View style={styles.daysContainer}>{calendarGrid}</View>
</View>
{/* 选中日期的事件列表 */}
{selectedDate && (
<View style={styles.eventListCard}>
<Text style={styles.eventListTitle}>
{selectedDate} 的日程
</Text>
{selectedEvents.length > 0 ? (
selectedEvents.map((event) => (
<TouchableOpacity
key={event.id}
style={styles.eventItem}
onPress={() => onEventPress?.(event)}
>
<View
style={[
styles.eventTypeDot,
{ backgroundColor: event.color }
]}
/>
<View style={styles.eventContent}>
<Text style={styles.eventTitle}>{event.title}</Text>
<Text
style={[
styles.eventType,
{ color: event.color }
]}
>
{EVENT_LABELS[event.type].zh}
</Text>
</View>
</TouchableOpacity>
))
) : (
<Text style={styles.noEventText}>暂无日程安排</Text>
)}
</View>
)}
{/* 事件类型说明 */}
<View style={styles.legendCard}>
<Text style={styles.legendTitle}>事件类型</Text>
{Object.entries(EventType).map(([_, type]) => (
<View key={type} style={styles.legendItem}>
<View
style={[
styles.legendDot,
{ backgroundColor: EVENT_COLORS[type] }
]}
/>
<Text style={styles.legendLabel}>
{EVENT_LABELS[type].zh}
</Text>
</View>
))}
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
calendarCard: {
backgroundColor: '#fff',
borderRadius: 16,
margin: 16,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
monthNavigation: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
},
monthText: {
fontSize: 18,
fontWeight: '700',
color: '#1a1a1a',
},
navButton: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: '#f0f0f0',
justifyContent: 'center',
alignItems: 'center',
},
navButtonText: {
fontSize: 22,
color: '#007AFF',
fontWeight: '600',
},
weekHeader: {
flexDirection: 'row',
marginBottom: 8,
},
weekDayCell: {
flex: 1,
height: 32,
justifyContent: 'center',
alignItems: 'center',
},
weekDayText: {
fontSize: 13,
fontWeight: '600',
color: '#666',
},
daysContainer: {
marginTop: 4,
},
weekRow: {
flexDirection: 'row',
marginBottom: 4,
},
dayCell: {
flex: 1,
minHeight: 52,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 8,
margin: 1,
position: 'relative',
},
dayCellDisabled: {
opacity: 0.3,
},
dayCellToday: {
borderWidth: 2,
borderColor: '#007AFF',
},
dayCellSelected: {
backgroundColor: '#F0F8FF',
borderWidth: 1,
borderColor: '#007AFF',
},
dayCellWithEvents: {
paddingBottom: 4,
},
dayText: {
fontSize: 16,
color: '#1a1a1a',
fontWeight: '500',
marginBottom: 2,
},
dayTextDisabled: {
color: '#999',
},
dayTextToday: {
color: '#007AFF',
fontWeight: '700',
},
dayTextSelected: {
color: '#007AFF',
fontWeight: '700',
},
dotsContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 2,
},
eventDot: {
width: 5,
height: 5,
borderRadius: 2.5,
},
moreDot: {
width: 14,
height: 14,
borderRadius: 7,
backgroundColor: '#E0E0E0',
justifyContent: 'center',
alignItems: 'center',
},
moreDotText: {
fontSize: 10,
color: '#666',
fontWeight: '700',
},
eventListCard: {
backgroundColor: '#fff',
borderRadius: 12,
margin: 16,
marginTop: 8,
padding: 16,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 2,
},
eventListTitle: {
fontSize: 16,
fontWeight: '700',
color: '#1a1a1a',
marginBottom: 12,
},
eventItem: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
},
eventTypeDot: {
width: 12,
height: 12,
borderRadius: 6,
marginRight: 12,
},
eventContent: {
flex: 1,
},
eventTitle: {
fontSize: 15,
fontWeight: '600',
color: '#1a1a1a',
marginBottom: 2,
},
eventType: {
fontSize: 12,
},
noEventText: {
fontSize: 14,
color: '#999',
textAlign: 'center',
paddingVertical: 20,
},
legendCard: {
backgroundColor: '#fff',
borderRadius: 12,
margin: 16,
marginTop: 8,
padding: 16,
},
legendTitle: {
fontSize: 16,
fontWeight: '700',
color: '#1a1a1a',
marginBottom: 12,
},
legendItem: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
legendDot: {
width: 12,
height: 12,
borderRadius: 6,
marginRight: 10,
},
legendLabel: {
fontSize: 14,
color: '#333',
},
});
性能优化策略
渲染优化
-
限制标记数量
- 单日最多显示 3 个标记点
- 超出部分显示 “+” 号
-
虚拟化处理
- 使用
useMemo缓存计算结果 - 避免在渲染中创建新对象
- 使用
-
事件数据分页
// 分页加载事件数据 const loadEventsForMonth = async (year: number, month: number) => { const startDate = `${year}-${String(month + 1).padStart(2, '0')}-01`; const endDate = `${year}-${String(month + 1).padStart(2, '0')}-31`; const events = await api.getEvents({ startDate, endDate }); eventManager.addEvents(events); };
内存优化
-
及时清理
// 组件卸载时清理 useEffect(() => { return () => { eventManager.clear(); }; }, []); -
懒加载事件
- 只加载当前月份的事件
- 切换月份时才加载新数据
OpenHarmony 适配要点
-
使用绝对定位
- 标记点使用绝对定位避免布局抖动
-
避免过度嵌套
- 减少组件层级提升性能
-
简化样式
- 避免使用复杂阴影和渐变
总结
本文介绍了在 OpenHarmony 平台上实现日历日程标记系统的完整方案,包括数据结构设计、事件管理、性能优化等关键技术点。
相关资源
- 完整项目 Demo
- OpenHarmony 跨平台社区
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
📕个人领域 :Linux/C++/java/AI
🚀 个人主页 :有点流鼻涕 · CSDN
💬 座右铭 : “向光而行,沐光而生。”

更多推荐

所有评论(0)