【HarmonyOS】DAY22:React Native for OpenHarmony:Calendar日程标记与事件管理

引言

在上一篇博文中,我们实现了基础的Calendar日历组件。今天,我们将深入探讨如何为日历添加强大的日程标记和事件管理功能。这将是构建生产力应用、日程管理工具或企业应用的关键组成部分。通过React Native for OpenHarmony,我们可以创建既美观又实用的日程管理系统。

一、事件数据结构设计

1.1 定义事件数据模型

// types/CalendarEvent.ts
export interface CalendarEvent {
  id: string;
  title: string;
  description?: string;
  startDate: Date;
  endDate: Date;
  color: string;
  category?: EventCategory;
  location?: string;
  isAllDay?: boolean;
  reminder?: ReminderSetting;
  recurrence?: RecurrenceRule;
  attachments?: Attachment[];
}

export type EventCategory = 
  | 'meeting' 
  | 'personal' 
  | 'work' 
  | 'health' 
  | 'education'
  | 'travel' 
  | 'family';

export interface ReminderSetting {
  enabled: boolean;
  minutesBefore: number; // 提前提醒分钟数
  notificationType: 'push' | 'email' | 'both';
}

export interface RecurrenceRule {
  frequency: 'daily' | 'weekly' | 'monthly' | 'yearly';
  interval: number; // 重复间隔
  endDate?: Date; // 结束日期
  occurrences?: number; // 发生次数
  weekDays?: number[]; // 周几重复 (0-6)
}

export interface Attachment {
  id: string;
  name: string;
  uri: string;
  type: 'image' | 'document' | 'audio' | 'video';
  size: number;
}

// 按日期分组的事件数据结构
export interface GroupedEvents {
  [dateString: string]: CalendarEvent[];
}

1.2 事件管理服务类

// services/EventManager.ts
import { CalendarEvent, GroupedEvents } from '../types/CalendarEvent';

class EventManager {
  private events: Map<string, CalendarEvent> = new Map();
  private groupedEvents: GroupedEvents = {};

  // 添加事件
  addEvent(event: CalendarEvent): string {
    const id = `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    const newEvent = { ...event, id };
    
    this.events.set(id, newEvent);
    this.updateGroupedEvents(newEvent);
    
    return id;
  }

  // 更新事件
  updateEvent(id: string, updates: Partial<CalendarEvent>): boolean {
    const event = this.events.get(id);
    if (!event) return false;

    const updatedEvent = { ...event, ...updates };
    this.events.set(id, updatedEvent);
    
    // 重新分组
    this.regroupEvents();
    
    return true;
  }

  // 删除事件
  deleteEvent(id: string): boolean {
    const event = this.events.get(id);
    if (!event) return false;

    this.events.delete(id);
    this.regroupEvents();
    
    return true;
  }

  // 获取某天的事件
  getEventsByDate(date: Date): CalendarEvent[] {
    const dateString = this.formatDate(date);
    return this.groupedEvents[dateString] || [];
  }

  // 获取某月的事件
  getEventsByMonth(year: number, month: number): GroupedEvents {
    const result: GroupedEvents = {};
    
    this.events.forEach(event => {
      const eventDate = new Date(event.startDate);
      if (eventDate.getFullYear() === year && eventDate.getMonth() === month) {
        const dateString = this.formatDate(eventDate);
        if (!result[dateString]) {
          result[dateString] = [];
        }
        result[dateString].push(event);
      }
    });
    
    return result;
  }

  // 获取即将发生的事件(未来7天)
  getUpcomingEvents(limit?: number): CalendarEvent[] {
    const now = new Date();
    const upcomingEvents: CalendarEvent[] = [];
    
    this.events.forEach(event => {
      if (event.startDate > now) {
        upcomingEvents.push(event);
      }
    });
    
    // 按时间排序
    upcomingEvents.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
    
    return limit ? upcomingEvents.slice(0, limit) : upcomingEvents;
  }

  // 搜索事件
  searchEvents(query: string): CalendarEvent[] {
    const results: CalendarEvent[] = [];
    const lowerQuery = query.toLowerCase();
    
    this.events.forEach(event => {
      if (
        event.title.toLowerCase().includes(lowerQuery) ||
        (event.description && event.description.toLowerCase().includes(lowerQuery)) ||
        (event.location && event.location.toLowerCase().includes(lowerQuery))
      ) {
        results.push(event);
      }
    });
    
    return results;
  }

  // 私有方法:更新分组事件
  private updateGroupedEvents(event: CalendarEvent): void {
    const dateString = this.formatDate(new Date(event.startDate));
    
    if (!this.groupedEvents[dateString]) {
      this.groupedEvents[dateString] = [];
    }
    
    // 确保不重复添加
    const existingIndex = this.groupedEvents[dateString].findIndex(e => e.id === event.id);
    if (existingIndex === -1) {
      this.groupedEvents[dateString].push(event);
    } else {
      this.groupedEvents[dateString][existingIndex] = event;
    }
    
    // 按开始时间排序
    this.groupedEvents[dateString].sort((a, b) => 
      a.startDate.getTime() - b.startDate.getTime()
    );
  }

  // 私有方法:重新分组所有事件
  private regroupEvents(): void {
    this.groupedEvents = {};
    
    this.events.forEach(event => {
      this.updateGroupedEvents(event);
    });
  }

  // 私有方法:格式化日期为字符串
  private formatDate(date: Date): string {
    return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
  }

  // 导入/导出功能
  exportEvents(): string {
    const eventsArray = Array.from(this.events.values());
    return JSON.stringify(eventsArray);
  }

  importEvents(jsonString: string): boolean {
    try {
      const eventsArray: CalendarEvent[] = JSON.parse(jsonString);
      
      eventsArray.forEach(event => {
        // 转换字符串日期为Date对象
        event.startDate = new Date(event.startDate);
        event.endDate = new Date(event.endDate);
        this.addEvent(event);
      });
      
      return true;
    } catch (error) {
      console.error('导入事件失败:', error);
      return false;
    }
  }
}

export default new EventManager();

二、增强型日历组件实现

2.1 支持事件标记的日历组件

// components/EnhancedCalendar.jsx
import React, { useState, useEffect, useMemo } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  ScrollView,
  Dimensions,
  Animated,
} from 'react-native';
import EventManager from '../services/EventManager';
import { CalendarEvent } from '../types/CalendarEvent';

const { width } = Dimensions.get('window');

const EnhancedCalendar = ({
  onDateSelect,
  onEventSelect,
  initialDate = new Date(),
  showMonthOverview = true,
}) => {
  const [currentDate, setCurrentDate] = useState(initialDate);
  const [currentMonth, setCurrentMonth] = useState(initialDate.getMonth());
  const [currentYear, setCurrentYear] = useState(initialDate.getFullYear());
  const [selectedDate, setSelectedDate] = useState(initialDate);
  const [monthEvents, setMonthEvents] = useState({});
  const [expandedDate, setExpandedDate] = useState(null);
  const fadeAnim = useState(new Animated.Value(0))[0];

  // 加载月份事件
  useEffect(() => {
    loadMonthEvents();
  }, [currentYear, currentMonth]);

  const loadMonthEvents = () => {
    const events = EventManager.getEventsByMonth(currentYear, currentMonth);
    setMonthEvents(events);
  };

  // 渲染日期格子(带事件标记)
  const renderDayCell = (day, isEmpty = false) => {
    if (isEmpty) {
      return <View style={styles.emptyCell} />;
    }

    const date = new Date(currentYear, currentMonth, day);
    const dateStr = formatDate(date);
    const dayEvents = monthEvents[dateStr] || [];
    const isSelected = isSameDay(date, selectedDate);
    const isToday = isSameDay(date, new Date());

    // 事件标记颜色
    const eventColors = dayEvents.map(event => event.color);
    const uniqueColors = [...new Set(eventColors)];

    return (
      <TouchableOpacity
        style={[
          styles.dayCell,
          isSelected && styles.selectedCell,
          isToday && !isSelected && styles.todayCell,
        ]}
        onPress={() => handleDateSelect(date)}
        onLongPress={() => setExpandedDate(expandedDate === dateStr ? null : dateStr)}
      >
        <View style={styles.dayHeader}>
          <Text style={[
            styles.dayNumber,
            isSelected && styles.selectedText,
            isToday && styles.todayText,
          ]}>
            {day}
          </Text>
          
          {dayEvents.length > 0 && (
            <View style={styles.eventIndicators}>
              {uniqueColors.slice(0, 3).map((color, index) => (
                <View
                  key={index}
                  style={[styles.eventDot, { backgroundColor: color }]}
                />
              ))}
              {uniqueColors.length > 3 && (
                <Text style={styles.moreEvents}>+{uniqueColors.length - 3}</Text>
              )}
            </View>
          )}
        </View>

        {/* 展开显示事件详情 */}
        {expandedDate === dateStr && dayEvents.length > 0 && (
          <Animated.View 
            style={[
              styles.expandedEvents,
              { opacity: fadeAnim }
            ]}
          >
            <ScrollView style={styles.eventsList}>
              {dayEvents.slice(0, 3).map(event => (
                <TouchableOpacity
                  key={event.id}
                  style={[styles.eventPreview, { borderLeftColor: event.color }]}
                  onPress={() => onEventSelect?.(event)}
                >
                  <Text style={styles.eventTime}>
                    {formatTime(event.startDate)}
                  </Text>
                  <Text style={styles.eventTitle} numberOfLines={1}>
                    {event.title}
                  </Text>
                </TouchableOpacity>
              ))}
              {dayEvents.length > 3 && (
                <Text style={styles.viewMoreText}>
                  查看全部 {dayEvents.length} 个事件
                </Text>
              )}
            </ScrollView>
          </Animated.View>
        )}
      </TouchableOpacity>
    );
  };

  // 月份概览视图
  const renderMonthOverview = () => {
    if (!showMonthOverview) return null;

    const monthEventsArray = Object.values(monthEvents).flat();
    const categories = {};
    
    monthEventsArray.forEach(event => {
      if (event.category) {
        categories[event.category] = (categories[event.category] || 0) + 1;
      }
    });

    return (
      <View style={styles.monthOverview}>
        <Text style={styles.overviewTitle}>本月概览</Text>
        <View style={styles.statsContainer}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{monthEventsArray.length}</Text>
            <Text style={styles.statLabel}>总事件</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>
              {getUpcomingEventsCount(monthEventsArray)}
            </Text>
            <Text style={styles.statLabel}>即将发生</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>
              {Object.keys(categories).length}
            </Text>
            <Text style={styles.statLabel}>类别</Text>
          </View>
        </View>
        
        {/* 事件类别分布 */}
        {Object.keys(categories).length > 0 && (
          <View style={styles.categoriesContainer}>
            {Object.entries(categories).slice(0, 4).map(([category, count], index) => (
              <View key={index} style={styles.categoryBadge}>
                <Text style={styles.categoryText}>
                  {getCategoryLabel(category)} ({count})
                </Text>
              </View>
            ))}
          </View>
        )}
      </View>
    );
  };

  // 处理日期选择
  const handleDateSelect = (date) => {
    setSelectedDate(date);
    setExpandedDate(null);
    onDateSelect?.(date);
    
    // 动画效果
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true,
    }).start();
  };

  // 辅助函数
  const isSameDay = (date1, date2) => {
    return (
      date1.getDate() === date2.getDate() &&
      date1.getMonth() === date2.getMonth() &&
      date1.getFullYear() === date2.getFullYear()
    );
  };

  const formatDate = (date) => {
    return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
  };

  const formatTime = (date) => {
    return date.toLocaleTimeString('zh-CN', { 
      hour: '2-digit', 
      minute: '2-digit' 
    });
  };

  const getUpcomingEventsCount = (events) => {
    const now = new Date();
    return events.filter(event => event.startDate > now).length;
  };

  const getCategoryLabel = (category) => {
    const labels = {
      meeting: '会议',
      personal: '个人',
      work: '工作',
      health: '健康',
      education: '学习',
      travel: '旅行',
      family: '家庭',
    };
    return labels[category] || category;
  };

  return (
    <View style={styles.container}>
      {/* 日历头部(月份切换) */}
      <View style={styles.calendarHeader}>
        <TouchableOpacity onPress={goToPreviousMonth}>
          <Text style={styles.navButton}>{'<'}</Text>
        </TouchableOpacity>
        
        <Text style={styles.monthYear}>
          {currentYear}年 {currentMonth + 1}月
        </Text>
        
        <TouchableOpacity onPress={goToNextMonth}>
          <Text style={styles.navButton}>{'>'}</Text>
        </TouchableOpacity>
      </View>

      {/* 星期标题 */}
      <View style={styles.weekDays}>
        {['日', '一', '二', '三', '四', '五', '六'].map((day, index) => (
          <Text key={index} style={styles.weekDayText}>{day}</Text>
        ))}
      </View>

      {/* 日期网格 */}
      <View style={styles.daysGrid}>
        {renderDaysGrid()}
      </View>

      {/* 月份概览 */}
      {renderMonthOverview()}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    backgroundColor: '#FFFFFF',
    borderRadius: 16,
    padding: 20,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 8,
    elevation: 5,
  },
  calendarHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 20,
  },
  monthYear: {
    fontSize: 20,
    fontWeight: '600',
    color: '#1C1C1E',
  },
  navButton: {
    fontSize: 24,
    color: '#007AFF',
    paddingHorizontal: 16,
  },
  weekDays: {
    flexDirection: 'row',
    marginBottom: 10,
  },
  weekDayText: {
    flex: 1,
    textAlign: 'center',
    fontSize: 14,
    fontWeight: '500',
    color: '#8E8E93',
  },
  daysGrid: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  dayCell: {
    width: width / 7 - 10,
    minHeight: 80,
    padding: 8,
    borderWidth: 1,
    borderColor: '#F2F2F7',
    borderRadius: 8,
    margin: 2,
  },
  emptyCell: {
    width: width / 7 - 10,
    height: 80,
    margin: 2,
  },
  selectedCell: {
    backgroundColor: '#E3F2FD',
    borderColor: '#007AFF',
  },
  todayCell: {
    borderColor: '#FF9500',
  },
  dayHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'flex-start',
  },
  dayNumber: {
    fontSize: 16,
    fontWeight: '500',
    color: '#3C3C43',
  },
  selectedText: {
    color: '#007AFF',
    fontWeight: '600',
  },
  todayText: {
    color: '#FF9500',
    fontWeight: '600',
  },
  eventIndicators: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  eventDot: {
    width: 6,
    height: 6,
    borderRadius: 3,
    marginLeft: 2,
  },
  moreEvents: {
    fontSize: 10,
    color: '#8E8E93',
    marginLeft: 2,
  },
  expandedEvents: {
    marginTop: 8,
    maxHeight: 120,
  },
  eventsList: {
    maxHeight: 100,
  },
  eventPreview: {
    backgroundColor: '#F9F9F9',
    padding: 6,
    borderRadius: 4,
    marginBottom: 4,
    borderLeftWidth: 3,
  },
  eventTime: {
    fontSize: 10,
    color: '#8E8E93',
  },
  eventTitle: {
    fontSize: 12,
    color: '#1C1C1E',
    marginTop: 2,
  },
  viewMoreText: {
    fontSize: 11,
    color: '#007AFF',
    textAlign: 'center',
    marginTop: 4,
  },
  monthOverview: {
    marginTop: 24,
    paddingTop: 20,
    borderTopWidth: 1,
    borderTopColor: '#F2F2F7',
  },
  overviewTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1C1C1E',
    marginBottom: 16,
  },
  statsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginBottom: 20,
  },
  statItem: {
    alignItems: 'center',
  },
  statNumber: {
    fontSize: 24,
    fontWeight: '700',
    color: '#007AFF',
  },
  statLabel: {
    fontSize: 12,
    color: '#8E8E93',
    marginTop: 4,
  },
  categoriesContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  categoryBadge: {
    backgroundColor: '#F2F2F7',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
    marginRight: 8,
    marginBottom: 8,
  },
  categoryText: {
    fontSize: 12,
    color: '#3C3C43',
  },
});

export default EnhancedCalendar;

2.2 事件详情与编辑组件

// components/EventDetailModal.jsx
import React, { useState, useEffect } from 'react';
import {
  Modal,
  View,
  Text,
  TextInput,
  TouchableOpacity,
  ScrollView,
  StyleSheet,
  Platform,
  Alert,
} from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
import { CalendarEvent, EventCategory } from '../types/CalendarEvent';
import EventManager from '../services/EventManager';

const EventDetailModal = ({
  visible,
  event,
  date,
  onClose,
  onSave,
  onDelete,
}) => {
  const [editing, setEditing] = useState(!event);
  const [formData, setFormData] = useState(getInitialFormData(event, date));
  const [showStartPicker, setShowStartPicker] = useState(false);
  const [showEndPicker, setShowEndPicker] = useState(false);

  const categories: EventCategory[] = [
    'meeting',
    'personal',
    'work',
    'health',
    'education',
    'travel',
    'family',
  ];

  const categoryColors = {
    meeting: '#FF3B30',
    personal: '#34C759',
    work: '#007AFF',
    health: '#FF9500',
    education: '#5856D6',
    travel: '#FF2D55',
    family: '#AF52DE',
  };

  const categoryLabels = {
    meeting: '会议',
    personal: '个人',
    work: '工作',
    health: '健康',
    education: '学习',
    travel: '旅行',
    family: '家庭',
  };

  const handleSave = () => {
    if (!formData.title.trim()) {
      Alert.alert('提示', '请输入事件标题');
      return;
    }

    if (formData.startDate >= formData.endDate) {
      Alert.alert('提示', '结束时间必须晚于开始时间');
      return;
    }

    const eventData: CalendarEvent = {
      id: event?.id || `event_${Date.now()}`,
      title: formData.title,
      description: formData.description,
      startDate: formData.startDate,
      endDate: formData.endDate,
      color: categoryColors[formData.category] || '#007AFF',
      category: formData.category,
      location: formData.location,
      isAllDay: formData.isAllDay,
    };

    if (event) {
      EventManager.updateEvent(event.id, eventData);
    } else {
      EventManager.addEvent(eventData);
    }

    onSave?.(eventData);
    onClose();
  };

  const handleDelete = () => {
    Alert.alert(
      '删除事件',
      '确定要删除这个事件吗?',
      [
        { text: '取消', style: 'cancel' },
        {
          text: '删除',
          style: 'destructive',
          onPress: () => {
            if (event) {
              EventManager.deleteEvent(event.id);
              onDelete?.(event.id);
            }
            onClose();
          },
        },
      ]
    );
  };

  const renderDateTimePicker = () => {
    if (Platform.OS === 'ios') {
      return (
        <View style={styles.dateTimeSection}>
          <View style={styles.timeRow}>
            <TouchableOpacity
              style={styles.timeButton}
              onPress={() => setShowStartPicker(true)}
            >
              <Text style={styles.timeLabel}>开始时间</Text>
              <Text style={styles.timeValue}>
                {formatDateTime(formData.startDate)}
              </Text>
            </TouchableOpacity>

            <TouchableOpacity
              style={styles.timeButton}
              onPress={() => setShowEndPicker(true)}
            >
              <Text style={styles.timeLabel}>结束时间</Text>
              <Text style={styles.timeValue}>
                {formatDateTime(formData.endDate)}
              </Text>
            </TouchableOpacity>
          </View>

          {showStartPicker && (
            <DateTimePicker
              value={formData.startDate}
              mode="datetime"
              display="spinner"
              onChange={(event, date) => {
                if (date) {
                  setFormData({ ...formData, startDate: date });
                }
                setShowStartPicker(false);
              }}
            />
          )}

          {showEndPicker && (
            <DateTimePicker
              value={formData.endDate}
              mode="datetime"
              display="spinner"
              onChange={(event, date) => {
                if (date) {
                  setFormData({ ...formData, endDate: date });
                }
                setShowEndPicker(false);
              }}
            />
          )}
        </View>
      );
    }

    return (
      <View style={styles.dateTimeSection}>
        <TouchableOpacity
          style={styles.timeButton}
          onPress={() => setShowStartPicker(true)}
        >
          <Text style={styles.timeLabel}>开始时间</Text>
          <Text style={styles.timeValue}>
            {formatDateTime(formData.startDate)}
          </Text>
        </TouchableOpacity>

        <TouchableOpacity
          style={styles.timeButton}
          onPress={() => setShowEndPicker(true)}
        >
          <Text style={styles.timeLabel}>结束时间</Text>
          <Text style={styles.timeValue}>
            {formatDateTime(formData.endDate)}
          </Text>
        </TouchableOpacity>

        {showStartPicker && (
          <DateTimePicker
            value={formData.startDate}
            mode="datetime"
            onChange={(event, date) => {
              setShowStartPicker(false);
              if (date) {
                setFormData({ ...formData, startDate: date });
              }
            }}
          />
        )}

        {showEndPicker && (
          <DateTimePicker
            value={formData.endDate}
            mode="datetime"
            onChange={(event, date) => {
              setShowEndPicker(false);
              if (date) {
                setFormData({ ...formData, endDate: date });
              }
            }}
          />
        )}
      </View>
    );
  };

  const renderCategorySelector = () => (
    <View style={styles.section}>
      <Text style={styles.sectionTitle}>事件类别</Text>
      <ScrollView 
        horizontal 
        showsHorizontalScrollIndicator={false}
        style={styles.categoriesScroll}
      >
        {categories.map((category) => (
          <TouchableOpacity
            key={category}
            style={[
              styles.categoryButton,
              formData.category === category && {
                backgroundColor: categoryColors[category],
              },
            ]}
            onPress={() => setFormData({ ...formData, category })}
          >
            <Text
              style={[
                styles.categoryButtonText,
                formData.category === category && styles.categoryButtonTextActive,
              ]}
            >
              {categoryLabels[category]}
            </Text>
          </TouchableOpacity>
        ))}
      </ScrollView>
    </View>
  );

  return (
    <Modal
      visible={visible}
      animationType="slide"
      presentationStyle="pageSheet"
      onRequestClose={onClose}
    >
      <View style={styles.modalContainer}>
        <View style={styles.header}>
          <TouchableOpacity onPress={onClose}>
            <Text style={styles.cancelButton}>取消</Text>
          </TouchableOpacity>
          
          <Text style={styles.title}>
            {editing ? (event ? '编辑事件' : '新建事件') : '事件详情'}
          </Text>
          
          <TouchableOpacity onPress={editing ? handleSave : () => setEditing(true)}>
            <Text style={styles.saveButton}>
              {editing ? '保存' : '编辑'}
            </Text>
          </TouchableOpacity>
        </View>

        <ScrollView style={styles.content}>
          {editing ? (
            <>
              <View style={styles.section}>
                <TextInput
                  style={styles.titleInput}
                  placeholder="事件标题"
                  value={formData.title}
                  onChangeText={(text) => setFormData({ ...formData, title: text })}
                  maxLength={100}
                />
              </View>

              {renderDateTimePicker()}

              <View style={styles.section}>
                <TextInput
                  style={styles.descriptionInput}
                  placeholder="添加描述(可选)"
                  value={formData.description}
                  onChangeText={(text) => setFormData({ ...formData, description: text })}
                  multiline
                  numberOfLines={4}
                  textAlignVertical="top"
                />
              </View>

              <View style={styles.section}>
                <TextInput
                  style={styles.locationInput}
                  placeholder="添加地点(可选)"
                  value={formData.location}
                  onChangeText={(text) => setFormData({ ...formData, location: text })}
                />
              </View>

              {renderCategorySelector()}

              <View style={styles.section}>
                <TouchableOpacity
                  style={styles.allDayRow}
                  onPress={() => setFormData({ ...formData, isAllDay: !formData.isAllDay })}
                >
                  <View style={styles.checkbox}>
                    {formData.isAllDay && <View style={styles.checkboxInner} />}
                  </View>
                  <Text style={styles.allDayText}>全天事件</Text>
                </TouchableOpacity>
              </View>
            </>
          ) : (
            <>
              <View style={styles.eventHeader}>
                <View style={[styles.colorIndicator, { backgroundColor: event.color }]} />
                <Text style={styles.eventTitle}>{event.title}</Text>
              </View>

              <View style={styles.detailSection}>
                <Text style={styles.detailLabel}>时间</Text>
                <Text style={styles.detailValue}>
                  {formatEventTime(event.startDate, event.endDate, event.isAllDay)}
                </Text>
              </View>

              {event.category && (
                <View style={styles.detailSection}>
                  <Text style={styles.detailLabel}>类别</Text>
                  <View style={[styles.categoryBadge, { backgroundColor: event.color }]}>
                    <Text style={styles.categoryBadgeText}>
                      {categoryLabels[event.category]}
                    </Text>
                  </View>
                </View>
              )}

              {event.location && (
                <View style={styles.detailSection}>
                  <Text style={styles.detailLabel}>地点</Text>
                  <Text style={styles.detailValue}>{event.location}</Text>
                </View>
              )}

              {event.description && (
                <View style={styles.detailSection}>
                  <Text style={styles.detailLabel}>描述</Text>
                  <Text style={styles.descriptionText}>{event.description}</Text>
                </View>
              )}
            </>
          )}
        </ScrollView>

        {!editing && (
          <TouchableOpacity
            style={styles.deleteButton}
            onPress={handleDelete}
          >
            <Text style={styles.deleteButtonText}>删除事件</Text>
          </TouchableOpacity>
        )}
      </View>
    </Modal>
  );
};

// 辅助函数
function getInitialFormData(event, date) {
  if (event) {
    return {
      title: event.title,
      description: event.description || '',
      startDate: new Date(event.startDate),
      endDate: new Date(event.endDate),
      category: event.category || 'personal',
      location: event.location || '',
      isAllDay: event.isAllDay || false,
    };
  }

  const startDate = date || new Date();
  const endDate = new Date(startDate.getTime() + 60 * 60 * 1000); // 默认1小时

  return {
    title: '',
    description: '',
    startDate,
    endDate,
    category: 'personal',
    location: '',
    isAllDay: false,
  };
}

function formatDateTime(date) {
  return date.toLocaleString('zh-CN', {
    month: 'short',
    day: 'numeric',
    hour: '2-digit',
    minute: '2-digit',
  });
}

function formatEventTime(startDate, endDate, isAllDay) {
  if (isAllDay) {
    return '全天';
  }

  const startStr = startDate.toLocaleTimeString('zh-CN', {
    hour: '2-digit',
    minute: '2-digit',
  });
  const endStr = endDate.toLocaleTimeString('zh-CN', {
    hour: '2-digit',
    minute: '2-digit',
  });

  return `${startStr} - ${endStr}`;
}

const styles = StyleSheet.create({
  modalContainer: {
    flex: 1,
    backgroundColor: '#FFFFFF',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#F2F2F7',
  },
  cancelButton: {
    fontSize: 17,
    color: '#007AFF',
  },
  title: {
    fontSize: 17,
    fontWeight: '600',
    color: '#000000',
  },
  saveButton: {
    fontSize: 17,
    fontWeight: '600',
    color: '#007AFF',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  section: {
    marginBottom: 24,
  },
  sectionTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#3C3C43',
    marginBottom: 12,
  },
  titleInput: {
    fontSize: 24,
    fontWeight: '600',
    color: '#1C1C1E',
    padding: 0,
  },
  dateTimeSection: {
    marginBottom: 24,
  },
  timeRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  timeButton: {
    flex: 1,
    padding: 12,
    backgroundColor: '#F2F2F7',
    borderRadius: 8,
    marginHorizontal: 4,
  },
  timeLabel: {
    fontSize: 12,
    color: '#8E8E93',
    marginBottom: 4,
  },
  timeValue: {
    fontSize: 16,
    fontWeight: '500',
    color: '#1C1C1E',
  },
  descriptionInput: {
    fontSize: 16,
    color: '#3C3C43',
    backgroundColor: '#F2F2F7',
    borderRadius: 8,
    padding: 12,
    minHeight: 100,
  },
  locationInput: {
    fontSize: 16,
    color: '#3C3C43',
    backgroundColor: '#F2F2F7',
    borderRadius: 8,
    padding: 12,
  },
  categoriesScroll: {
    flexDirection: 'row',
  },
  categoryButton: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 20,
    backgroundColor: '#F2F2F7',
    marginRight: 8,
  },
  categoryButtonText: {
    fontSize: 14,
    color: '#3C3C43',
  },
  categoryButtonTextActive: {
    color: '#FFFFFF',
  },
  allDayRow: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  checkbox: {
    width: 24,
    height: 24,
    borderRadius: 6,
    borderWidth: 2,
    borderColor: '#007AFF',
    marginRight: 12,
    justifyContent: 'center',
    alignItems: 'center',
  },
  checkboxInner: {
    width: 12,
    height: 12,
    borderRadius: 3,
    backgroundColor: '#007AFF',
  },
  allDayText: {
    fontSize: 16,
    color: '#1C1C1E',
  },
  eventHeader: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 24,
  },
  colorIndicator: {
    width: 12,
    height: 12,
    borderRadius: 6,
    marginRight: 12,
  },
  eventTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: '#1C1C1E',
  },
  detailSection: {
    marginBottom: 20,
  },
  detailLabel: {
    fontSize: 14,
    color: '#8E8E93',
    marginBottom: 4,
  },
  detailValue: {
    fontSize: 16,
    color: '#1C1C1E',
  },
  categoryBadge: {
    alignSelf: 'flex-start',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 16,
  },
  categoryBadgeText: {
    fontSize: 14,
    color: '#FFFFFF',
    fontWeight: '500',
  },
  descriptionText: {
    fontSize: 16,
    color: '#3C3C43',
    lineHeight: 24,
  },
  deleteButton: {
    margin: 16,
    padding: 16,
    backgroundColor: '#FF3B30',
    borderRadius: 12,
    alignItems: 'center',
  },
  deleteButtonText: {
    fontSize: 17,
    fontWeight: '600',
    color: '#FFFFFF',
  },
});

export default EventDetailModal;

三、主应用集成

3.1 完整的主应用示例

// App.jsx
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  StatusBar,
  TextInput,
  Alert,
} from 'react-native';
import EnhancedCalendar from './components/EnhancedCalendar';
import EventDetailModal from './components/EventDetailModal';
import EventManager from './services/EventManager';
import { CalendarEvent } from './types/CalendarEvent';

const App = () => {
  const [selectedDate, setSelectedDate] = useState(new Date());
  const [selectedEvent, setSelectedEvent] = useState(null);
  const [modalVisible, setModalVisible] = useState(false);
  const [searchQuery, setSearchQuery] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const [upcomingEvents, setUpcomingEvents] = useState([]);

  // 加载即将发生的事件
  useEffect(() => {
    loadUpcomingEvents();
  }, []);

  const loadUpcomingEvents = () => {
    const events = EventManager.getUpcomingEvents(5);
    setUpcomingEvents(events);
  };

  // 处理日期选择
  const handleDateSelect = (date) => {
    setSelectedDate(date);
    setSelectedEvent(null);
    setModalVisible(true);
  };

  // 处理事件选择
  const handleEventSelect = (event) => {
    setSelectedEvent(event);
    setModalVisible(true);
  };

  // 处理搜索
  const handleSearch = () => {
    if (searchQuery.trim()) {
      const results = EventManager.searchEvents(searchQuery);
      setSearchResults(results);
    } else {
      setSearchResults([]);
    }
  };

  // 清空搜索
  const clearSearch = () => {
    setSearchQuery('');
    setSearchResults([]);
  };

  // 处理事件保存
  const handleEventSave = (event) => {
    loadUpcomingEvents();
    Alert.alert('成功', '事件已保存');
  };

  // 处理事件删除
  const handleEventDelete = (eventId) => {
    loadUpcomingEvents();
    Alert.alert('成功', '事件已删除');
  };

  // 快速操作:添加今天的事件
  const addEventForToday = () => {
    setSelectedEvent(null);
    setSelectedDate(new Date());
    setModalVisible(true);
  };

  // 快速操作:添加明天的事件
  const addEventForTomorrow = () => {
    const tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);
    
    setSelectedEvent(null);
    setSelectedDate(tomorrow);
    setModalVisible(true);
  };

  // 渲染搜索结果
  const renderSearchResults = () => {
    if (searchResults.length === 0) return null;

    return (
      <View style={styles.searchResults}>
        <Text style={styles.resultsTitle}>搜索结果 ({searchResults.length})</Text>
        {searchResults.map(event => (
          <TouchableOpacity
            key={event.id}
            style={styles.resultItem}
            onPress={() => handleEventSelect(event)}
          >
            <View style={[styles.resultColor, { backgroundColor: event.color }]} />
            <View style={styles.resultContent}>
              <Text style={styles.resultTitle}>{event.title}</Text>
              <Text style={styles.resultDate}>
                {event.startDate.toLocaleDateString('zh-CN')}
              </Text>
            </View>
          </TouchableOpacity>
        ))}
      </View>
    );
  };

  // 渲染即将发生的事件
  const renderUpcomingEvents = () => {
    if (upcomingEvents.length === 0) return null;

    return (
      <View style={styles.upcomingSection}>
        <Text style={styles.sectionTitle}>即将发生</Text>
        {upcomingEvents.map(event => (
          <TouchableOpacity
            key={event.id}
            style={styles.upcomingItem}
            onPress={() => handleEventSelect(event)}
          >
            <View style={styles.upcomingTime}>
              <Text style={styles.upcomingTimeText}>
                {event.startDate.toLocaleTimeString('zh-CN', {
                  hour: '2-digit',
                  minute: '2-digit',
                })}
              </Text>
            </View>
            <View style={styles.upcomingContent}>
              <Text style={styles.upcomingTitle}>{event.title}</Text>
              <Text style={styles.upcomingCategory}>
                {getCategoryLabel(event.category)}
              </Text>
            </View>
            <View style={[styles.upcomingDot, { backgroundColor: event.color }]} />
          </TouchableOpacity>
        ))}
      </View>
    );
  };

  // 渲染快速操作
  const renderQuickActions = () => (
    <View style={styles.quickActions}>
      <Text style={styles.sectionTitle}>快速操作</Text>
      <View style={styles.actionButtons}>
        <TouchableOpacity
          style={styles.actionButton}
          onPress={addEventForToday}
        >
          <Text style={styles.actionButtonText}>今天</Text>
        </TouchableOpacity>
        
        <TouchableOpacity
          style={styles.actionButton}
          onPress={addEventForTomorrow}
        >
          <Text style={styles.actionButtonText}>明天</Text>
        </TouchableOpacity>
        
        <TouchableOpacity
          style={styles.actionButton}
          onPress={() => Alert.alert('功能开发中')}
        >
          <Text style={styles.actionButtonText}>导入</Text>
        </TouchableOpacity>
        
        <TouchableOpacity
          style={styles.actionButton}
          onPress={() => Alert.alert('功能开发中')}
        >
          <Text style={styles.actionButtonText}>分享</Text>
        </TouchableOpacity>
      </View>
    </View>
  );

  return (
    <View style={styles.container}>
      <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
      
      <ScrollView style={styles.scrollView}>
        {/* 头部 */}
        <View style={styles.header}>
          <Text style={styles.appTitle}>日程管理</Text>
          <Text style={styles.appSubtitle}>React Native for OpenHarmony</Text>
        </View>

        {/* 搜索框 */}
        <View style={styles.searchContainer}>
          <TextInput
            style={styles.searchInput}
            placeholder="搜索事件..."
            value={searchQuery}
            onChangeText={setSearchQuery}
            onSubmitEditing={handleSearch}
            returnKeyType="search"
          />
          {searchQuery ? (
            <TouchableOpacity onPress={clearSearch}>
              <Text style={styles.clearButton}>清除</Text>
            </TouchableOpacity>
          ) : (
            <TouchableOpacity onPress={handleSearch}>
              <Text style={styles.searchButton}>搜索</Text>
            </TouchableOpacity>
          )}
        </View>

        {/* 搜索结果 */}
        {renderSearchResults()}

        {/* 日历组件 */}
        <View style={styles.calendarContainer}>
          <EnhancedCalendar
            onDateSelect={handleDateSelect}
            onEventSelect={handleEventSelect}
            initialDate={selectedDate}
            showMonthOverview={true}
          />
        </View>

        {/* 快速操作 */}
        {renderQuickActions()}

        {/* 即将发生的事件 */}
        {renderUpcomingEvents()}

        {/* 统计信息 */}
        <View style={styles.statsContainer}>
          <View style={styles.statCard}>
            <Text style={styles.statNumber}>
              {Object.keys(EventManager.getEventsByMonth(
                new Date().getFullYear(),
                new Date().getMonth()
              )).length}
            </Text>
            <Text style={styles.statLabel}>本月事件</Text>
          </View>
          <View style={styles.statCard}>
            <Text style={styles.statNumber}>
              {EventManager.getUpcomingEvents().length}
            </Text>
            <Text style={styles.statLabel}>待办事件</Text>
          </View>
        </View>
      </ScrollView>

      {/* 底部添加按钮 */}
      <TouchableOpacity
        style={styles.addButton}
        onPress={addEventForToday}
      >
        <Text style={styles.addButtonIcon}>+</Text>
      </TouchableOpacity>

      {/* 事件详情/编辑模态框 */}
      <EventDetailModal
        visible={modalVisible}
        event={selectedEvent}
        date={selectedDate}
        onClose={() => {
          setModalVisible(false);
          setSelectedEvent(null);
        }}
        onSave={handleEventSave}
        onDelete={handleEventDelete}
      />
    </View>
  );
};

// 辅助函数
const getCategoryLabel = (category) => {
  const labels = {
    meeting: '会议',
    personal: '个人',
    work: '工作',
    health: '健康',
    education: '学习',
    travel: '旅行',
    family: '家庭',
  };
  return labels[category] || category;
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F2F2F7',
  },
  scrollView: {
    flex: 1,
  },
  header: {
    padding: 24,
    paddingTop: 48,
    backgroundColor: '#FFFFFF',
  },
  appTitle: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#1C1C1E',
    marginBottom: 4,
  },
  appSubtitle: {
    fontSize: 16,
    color: '#8E8E93',
  },
  searchContainer: {
    flexDirection: 'row',
    padding: 16,
    backgroundColor: '#FFFFFF',
    marginBottom: 1,
  },
  searchInput: {
    flex: 1,
    height: 44,
    backgroundColor: '#F2F2F7',
    borderRadius: 10,
    paddingHorizontal: 16,
    fontSize: 16,
    color: '#1C1C1E',
  },
  clearButton: {
    marginLeft: 12,
    paddingVertical: 12,
    paddingHorizontal: 16,
    color: '#FF3B30',
    fontSize: 16,
  },
  searchButton: {
    marginLeft: 12,
    paddingVertical: 12,
    paddingHorizontal: 16,
    color: '#007AFF',
    fontSize: 16,
    fontWeight: '500',
  },
  searchResults: {
    backgroundColor: '#FFFFFF',
    padding: 16,
    marginBottom: 16,
  },
  resultsTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1C1C1E',
    marginBottom: 12,
  },
  resultItem: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#F2F2F7',
  },
  resultColor: {
    width: 8,
    height: 8,
    borderRadius: 4,
    marginRight: 12,
  },
  resultContent: {
    flex: 1,
  },
  resultTitle: {
    fontSize: 16,
    color: '#1C1C1E',
    marginBottom: 4,
  },
  resultDate: {
    fontSize: 14,
    color: '#8E8E93',
  },
  calendarContainer: {
    padding: 16,
    backgroundColor: '#FFFFFF',
    marginBottom: 16,
  },
  quickActions: {
    backgroundColor: '#FFFFFF',
    padding: 16,
    marginBottom: 16,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1C1C1E',
    marginBottom: 16,
  },
  actionButtons: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  actionButton: {
    flex: 1,
    backgroundColor: '#F2F2F7',
    paddingVertical: 12,
    paddingHorizontal: 16,
    borderRadius: 12,
    marginHorizontal: 4,
    alignItems: 'center',
  },
  actionButtonText: {
    fontSize: 14,
    fontWeight: '500',
    color: '#007AFF',
  },
  upcomingSection: {
    backgroundColor: '#FFFFFF',
    padding: 16,
    marginBottom: 16,
  },
  upcomingItem: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#F2F2F7',
  },
  upcomingTime: {
    width: 60,
  },
  upcomingTimeText: {
    fontSize: 14,
    color: '#8E8E93',
  },
  upcomingContent: {
    flex: 1,
  },
  upcomingTitle: {
    fontSize: 16,
    color: '#1C1C1E',
    marginBottom: 4,
  },
  upcomingCategory: {
    fontSize: 12,
    color: '#8E8E93',
  },
  upcomingDot: {
    width: 8,
    height: 8,
    borderRadius: 4,
  },
  statsContainer: {
    flexDirection: 'row',
    padding: 16,
    backgroundColor: '#FFFFFF',
    marginBottom: 32,
  },
  statCard: {
    flex: 1,
    alignItems: 'center',
    padding: 16,
    backgroundColor: '#F2F2F7',
    borderRadius: 12,
    marginHorizontal: 8,
  },
  statNumber: {
    fontSize: 28,
    fontWeight: '700',
    color: '#007AFF',
    marginBottom: 8,
  },
  statLabel: {
    fontSize: 14,
    color: '#8E8E93',
  },
  addButton: {
    position: 'absolute',
    bottom: 32,
    right: 32,
    width: 56,
    height: 56,
    borderRadius: 28,
    backgroundColor: '#007AFF',
    justifyContent: 'center',
    alignItems: 'center',
    shadowColor: '#007AFF',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.3,
    shadowRadius: 8,
    elevation: 8,
  },
  addButtonIcon: {
    fontSize: 24,
    color: '#FFFFFF',
    fontWeight: 'bold',
  },
});

export default App;

四、OpenHarmony特定优化

4.1 本地存储与数据持久化

// utils/OpenHarmonyStorage.ts
import { promptAction } from '@ohos/data.storage';
import { BusinessError } from '@ohos.base';

class OpenHarmonyStorage {
  private preferences: any = null;

  // 初始化存储
  async init() {
    try {
      this.preferences = await promptAction.getPreferences('calendar_app');
      return true;
    } catch (error) {
      console.error('初始化存储失败:', error);
      return false;
    }
  }

  // 保存事件数据
  async saveEvents(events: any[]): Promise<boolean> {
    try {
      await this.preferences.put('calendar_events', JSON.stringify(events));
      await this.preferences.flush();
      return true;
    } catch (error) {
      console.error('保存事件数据失败:', error);
      return false;
    }
  }

  // 加载事件数据
  async loadEvents(): Promise<any[]> {
    try {
      const eventsJson = await this.preferences.get('calendar_events', '[]');
      return JSON.parse(eventsJson);
    } catch (error) {
      console.error('加载事件数据失败:', error);
      return [];
    }
  }

  // 保存用户设置
  async saveSettings(settings: any): Promise<boolean> {
    try {
      await this.preferences.put('user_settings', JSON.stringify(settings));
      await this.preferences.flush();
      return true;
    } catch (error) {
      console.error('保存用户设置失败:', error);
      return false;
    }
  }

  // 加载用户设置
  async loadSettings(): Promise<any> {
    try {
      const settingsJson = await this.preferences.get('user_settings', '{}');
      return JSON.parse(settingsJson);
    } catch (error) {
      console.error('加载用户设置失败:', error);
      return {};
    }
  }

  // 备份数据到文件
  async backupToFile(): Promise<string | null> {
    try {
      const events = await this.loadEvents();
      const settings = await this.loadSettings();
      
      const backupData = {
        events,
        settings,
        backupDate: new Date().toISOString(),
      };
      
      // 这里需要根据OpenHarmony的文件API实现具体的文件保存逻辑
      // 注意:需要申请文件存储权限
      return JSON.stringify(backupData);
    } catch (error) {
      console.error('备份数据失败:', error);
      return null;
    }
  }
}

export default new OpenHarmonyStorage();

4.2 通知与提醒集成

// utils/NotificationManager.ts
import { NotificationRequest, NotificationSlot, notification } from '@ohos.notification';

class NotificationManager {
  private static instance: NotificationManager;
  private notificationIds: Set<number> = new Set();

  static getInstance(): NotificationManager {
    if (!NotificationManager.instance) {
      NotificationManager.instance = new NotificationManager();
    }
    return NotificationManager.instance;
  }

  // 创建通知渠道
  async createNotificationChannel(): Promise<void> {
    try {
      const slot: NotificationSlot = {
        type: notification.SlotType.SOCIAL_COMMUNICATION,
        level: notification.SlotLevel.HIGH,
        vibrations: [200, 300, 200, 300],
        sound: 'reminder_tone.wav',
      };
      
      await notification.setNotificationSlot('calendar_reminders', slot);
    } catch (error) {
      console.error('创建通知渠道失败:', error);
    }
  }

  // 发送事件提醒
  async sendEventReminder(event: any): Promise<boolean> {
    try {
      const request: NotificationRequest = {
        content: {
          title: '事件提醒',
          text: `${event.title} 即将开始`,
          additionalText: event.description || '',
          contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        },
        id: this.generateNotificationId(),
        slotType: notification.SlotType.SOCIAL_COMMUNICATION,
      };

      await notification.publish(request);
      this.notificationIds.add(request.id);
      
      return true;
    } catch (error) {
      console.error('发送通知失败:', error);
      return false;
    }
  }

  // 发送每日摘要
  async sendDailyDigest(events: any[]): Promise<boolean> {
    try {
      if (events.length === 0) return true;

      const request: NotificationRequest = {
        content: {
          title: '今日日程',
          text: `您今天有 ${events.length} 个事件`,
          contentType: notification.ContentType.NOTIFICATION_CONTENT_LONG_TEXT,
          longText: this.formatEventsForNotification(events),
        },
        id: this.generateNotificationId(),
        slotType: notification.SlotType.SOCIAL_COMMUNICATION,
      };

      await notification.publish(request);
      return true;
    } catch (error) {
      console.error('发送每日摘要失败:', error);
      return false;
    }
  }

  // 取消所有通知
  async cancelAllNotifications(): Promise<void> {
    try {
      for (const id of this.notificationIds) {
        await notification.cancel(id);
      }
      this.notificationIds.clear();
    } catch (error) {
      console.error('取消通知失败:', error);
    }
  }

  // 私有方法
  private generateNotificationId(): number {
    return Math.floor(Math.random() * 1000000);
  }

  private formatEventsForNotification(events: any[]): string {
    return events.map(event => 
      `${event.startDate.toLocaleTimeString('zh-CN', { 
        hour: '2-digit', 
        minute: '2-digit' 
      })} - ${event.title}`
    ).join('\n');
  }
}

export default NotificationManager.getInstance();

五、性能优化与最佳实践

5.1 使用Memoization优化性能

// 优化日历渲染
const CalendarGrid = React.memo(({ year, month, events, onDayPress }) => {
  const days = useMemo(() => calculateDays(year, month), [year, month]);
  
  return (
    <View style={styles.grid}>
      {days.map((day, index) => (
        <DayCell
          key={index}
          day={day}
          events={events[day.dateString] || []}
          onPress={() => onDayPress(day)}
        />
      ))}
    </View>
  );
});

// 优化事件列表
const EventList = React.memo(({ events, onEventPress }) => {
  const sortedEvents = useMemo(() => 
    events.sort((a, b) => a.startDate - b.startDate), 
    [events]
  );

  return (
    <FlatList
      data={sortedEvents}
      keyExtractor={item => item.id}
      renderItem={({ item }) => (
        <EventItem event={item} onPress={onEventPress} />
      )}
      initialNumToRender={10}
      maxToRenderPerBatch={10}
      windowSize={5}
    />
  );
});

5.2 使用Context管理状态

// contexts/CalendarContext.jsx
import React, { createContext, useContext, useReducer } from 'react';
import EventManager from '../services/EventManager';

const CalendarContext = createContext();

const initialState = {
  events: {},
  selectedDate: new Date(),
  viewMode: 'month',
  filters: {
    categories: [],
    showCompleted: false,
  },
  isLoading: false,
};

function calendarReducer(state, action) {
  switch (action.type) {
    case 'SET_EVENTS':
      return { ...state, events: action.payload };
    case 'SET_DATE':
      return { ...state, selectedDate: action.payload };
    case 'SET_VIEW_MODE':
      return { ...state, viewMode: action.payload };
    case 'SET_FILTERS':
      return { ...state, filters: action.payload };
    case 'SET_LOADING':
      return { ...state, isLoading: action.payload };
    case 'ADD_EVENT':
      const newEvents = { ...state.events };
      const dateStr = formatDate(action.payload.startDate);
      if (!newEvents[dateStr]) newEvents[dateStr] = [];
      newEvents[dateStr].push(action.payload);
      return { ...state, events: newEvents };
    default:
      return state;
  }
}

export const CalendarProvider = ({ children }) => {
  const [state, dispatch] = useReducer(calendarReducer, initialState);

  const loadEvents = async (year, month) => {
    dispatch({ type: 'SET_LOADING', payload: true });
    
    try {
      const events = EventManager.getEventsByMonth(year, month);
      dispatch({ type: 'SET_EVENTS', payload: events });
    } catch (error) {
      console.error('加载事件失败:', error);
    } finally {
      dispatch({ type: 'SET_LOADING', payload: false });
    }
  };

  const addEvent = async (event) => {
    const eventId = EventManager.addEvent(event);
    dispatch({ type: 'ADD_EVENT', payload: { ...event, id: eventId } });
    return eventId;
  };

  const value = {
    state,
    dispatch,
    loadEvents,
    addEvent,
  };

  return (
    <CalendarContext.Provider value={value}>
      {children}
    </CalendarContext.Provider>
  );
};

export const useCalendar = () => {
  const context = useContext(CalendarContext);
  if (!context) {
    throw new Error('useCalendar必须在CalendarProvider内部使用');
  }
  return context;
};

// 辅助函数
function formatDate(date) {
  return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
}

六、总结

通过本篇文章,我们深入探讨了在React Native for OpenHarmony中实现完整的日历日程标记与事件管理系统。主要内容包括:

  1. 完善的事件数据结构设计 - 定义了灵活的事件模型,支持各种日程需求
  2. 增强的日历组件 - 实现了带事件标记、预览和交互的日历视图
  3. 完整的事件管理功能 - 包括创建、编辑、删除、搜索等操作
  4. OpenHarmony特定优化 - 利用平台特性实现本地存储和通知提醒
  5. 性能优化 - 使用Memoization、Context等React最佳实践

这个日历事件管理系统具有以下特点:

  • 跨平台兼容:基于React Native,可在OpenHarmony及其他平台上运行
  • 用户体验优秀:支持拖拽、长按、快捷操作等交互方式
  • 功能完整:涵盖日程管理的主要需求
  • 性能优化:针对移动设备进行了专门的性能优化
  • 可扩展性强:模块化设计,便于功能扩展

在下一篇博文中,我们将继续探索如何为这个日历应用添加更多高级功能,如:

  • 协同编辑与共享功能
  • 日历订阅与导入导出
  • 数据同步与云端备份
  • 高级筛选与智能推荐
  • 统计分析报告

希望这篇文章能帮助你构建功能强大的OpenHarmony日历应用!

欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐