ReactNative项目OpenHarmony三方库集成实战:react-native-calendar-events(读取不到日历里新增的事件,待排查)

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

项目基于 RN 0.72.90 开发

📋 前言

在移动应用开发中,日历事件管理是一项常见需求,特别是在日程管理、会议提醒、活动安排等场景中。react-native-calendar-events 是一个功能强大的日历事件库,提供了读取和写入系统日历的能力,支持创建、查询、更新和删除日历事件,是实现日程管理功能的理想选择。

🎯 库简介

基本信息

为什么需要日历事件库?

特性 原生方案 react-native-calendar-events
跨平台一致性 ⚠️ 需分别开发 ✅ 统一 API
权限管理 ⚠️ 手动处理 ✅ 自动处理
事件操作 ⚠️ 需封装 ✅ 开箱即用
重复规则 ⚠️ 复杂实现 ✅ 内置支持
HarmonyOS 支持 ⚠️ 需适配 ✅ 完善适配

核心功能

功能 说明 HarmonyOS 支持
requestPermissions 请求日历权限
checkPermissions 检查权限状态
findCalendars 获取所有日历
saveCalendar 创建日历
removeCalendar 删除日历
findEventById 根据ID查询事件
fetchAllEvents 获取所有事件
saveEvent 创建/更新事件
removeEvent 删除事件

兼容性验证

在以下环境验证通过:

  • RNOH : 0.72.90; SDK : HarmonyOS 6.0.0 Release SDK; IDE : DevEco Studio 6.0.2; ROM: 6.0.0

📦 安装步骤

1. 安装依赖

bash 复制代码
# RN 0.72 版本
npm install @react-native-ohos/react-native-calendar-events@2.2.1-rc.1

# 或者使用 yarn
yarn add @react-native-ohos/react-native-calendar-events@2.2.1-rc.1

2. 验证安装

安装完成后,检查 package.json 文件:

json 复制代码
{
  "dependencies": {
    "@react-native-ohos/react-native-calendar-events": "^2.2.1-rc.1"
  }
}

🔧 HarmonyOS 平台配置 ⭐

1. 引入原生端代码

打开 harmony/entry/oh-package.json5,添加以下依赖:

json5 复制代码
"dependencies": {
  "@react-native-ohos/react-native-calendar-events": "file:../../node_modules/@react-native-ohos/react-native-calendar-events/harmony/calendar_events.har"
}

点击右上角的 sync 按钮,或者在终端执行:

bash 复制代码
cd entry
ohpm install

2. 配置 CMakeLists

打开 entry/src/main/cpp/CMakeLists.txt,添加:

c 复制代码
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")

# RNOH_BEGIN: manual_package_linking_1
+ add_subdirectory("${OH_MODULES}/@react-native-ohos/react-native-calendar-events/src/main/cpp" ./calendar_events)
# RNOH_END: manual_package_linking_1

# RNOH_BEGIN: manual_package_linking_2
+ target_link_libraries(rnoh_app PUBLIC rnoh_calendar_events)
# RNOH_END: manual_package_linking_2

3. 引入 CalendarEventPackage

打开 entry/src/main/cpp/PackageProvider.cpp,添加:

cpp 复制代码
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
+ #include "CalendarEventsPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
      std::make_shared<RNOHGeneratedPackage>(ctx),
      + std::make_shared<CalendarEventsPackage>(ctx)
    };
}

打开 entry/src/main/ets/RNPackagesFactory.ets,添加:

typescript 复制代码
+ import { CalendarEventPackage } from '@react-native-ohos/react-native-calendar-events/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new SamplePackage(ctx),
    + new CalendarEventPackage(ctx)
  ];
}

4. 配置权限

entry/src/main/module.json5 中添加日历权限:

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.READ_CALENDAR",
        "reason": "$string:Access_calendar",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.WRITE_CALENDAR",
        "reason": "$string:Access_calendar",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when": "inuse"
        }
      }
    ]
  }
}

5. 添加权限说明

打开 entry/src/main/resources/base/element/string.json,添加权限说明:

json 复制代码
{
  "string": [
    {
      "name": "Access_calendar",
      "value": "用于访问和管理日历事件"
    }
  ]
}

📖 API 详解

requestPermissions - 请求日历权限

请求日历访问权限,必须在访问日历数据之前调用。

类型(readOnly?: boolean) => Promise<'authorized' | 'denied' | 'restricted' | 'undetermined'>

参数

  • readOnly: 是否只请求读取权限,默认为 false

返回值

  • authorized: 已授权
  • denied: 已拒绝
  • restricted: 受限制(如家长控制)
  • undetermined: 未确定

使用场景

  • 应用首次启动时请求权限
  • 用户拒绝后重新请求
tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const requestPermission = async () => {
  const status = await RNCalendarEvents.requestPermissions();
  if (status === 'authorized') {
    console.log('日历权限已授权');
  } else {
    console.log('日历权限被拒绝');
  }
};

checkPermissions - 检查权限状态

检查当前的日历权限状态,不会弹出权限请求对话框。

类型(readOnly?: boolean) => Promise<'authorized' | 'denied' | 'restricted' | 'undetermined'>

参数

  • readOnly: 是否只检查读取权限,默认为 false

使用场景

  • 在执行日历操作前检查权限
  • 根据权限状态显示不同的 UI
tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const checkPermission = async () => {
  const status = await RNCalendarEvents.checkPermissions();
  console.log('当前权限状态:', status);
  return status === 'authorized';
};

findCalendars - 获取所有日历

获取设备上所有可用的日历列表。

类型() => Promise<Calendar[]>

返回值:日历对象数组,包含以下属性:

属性 类型 说明
id string 日历唯一标识
title string 日历标题
source object 日历来源
color string 日历颜色
allowsModifications boolean 是否允许修改
type string 日历类型

使用场景

  • 显示日历列表供用户选择
  • 获取默认日历 ID
tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const getCalendars = async () => {
  const calendars = await RNCalendarEvents.findCalendars();
  calendars.forEach((calendar) => {
    console.log('日历:', calendar.title, 'ID:', calendar.id);
  });
  return calendars;
};

saveCalendar - 创建日历

创建一个新的日历。

类型(options: CalendarOptions) => Promise<string>

参数

属性 类型 必填 说明
title string 日历标题
type string 日历类型
displayName string 显示名称
color string 日历颜色

返回值:新创建日历的 ID

tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const createCalendar = async () => {
  const calendarId = await RNCalendarEvents.saveCalendar({
    title: '我的日历',
    type: 'local',
    displayName: '个人日程',
    color: '#FF5722',
  });
  console.log('创建日历成功, ID:', calendarId);
  return calendarId;
};

removeCalendar - 删除日历

根据 ID 删除指定的日历。

类型(id: string) => Promise<boolean>

参数

  • id: 日历 ID

返回值:是否删除成功

tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const deleteCalendar = async (calendarId: string) => {
  const success = await RNCalendarEvents.removeCalendar(calendarId);
  console.log('删除日历:', success ? '成功' : '失败');
  return success;
};

findEventById - 根据 ID 查询事件

根据事件 ID 获取单个事件的详细信息。

类型(id: string) => Promise<Event | null>

参数

  • id: 事件 ID

返回值 :事件对象,如果未找到则返回 null

事件对象属性

属性 类型 说明
id string 事件唯一标识
title string 事件标题
location string 事件地点
startDate string 开始时间
endDate string 结束时间
allDay boolean 是否全天事件
description string 事件描述
calendarId string 所属日历 ID
attendees array 参与者列表
recurrenceRule object 重复规则
reminders array 提醒列表
tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const getEventById = async (eventId: string) => {
  const event = await RNCalendarEvents.findEventById(eventId);
  if (event) {
    console.log('事件标题:', event.title);
    console.log('开始时间:', event.startDate);
  }
  return event;
};

fetchAllEvents - 获取所有事件

获取指定时间范围内的所有事件。

类型(startDate: string, endDate: string, calendars?: string[]) => Promise<Event[]>

参数

参数 类型 必填 说明
startDate string 开始时间(时间戳字符串)
endDate string 结束时间(时间戳字符串)
calendars string[] 要查询的日历 ID 列表

使用场景

  • 显示日程列表
  • 日历视图数据源
  • 查询特定时间段的事件
tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const getEvents = async () => {
  const now = Date.now();
  const startDate = now.toString();
  const endDate = (now + 7 * 24 * 60 * 60 * 1000).toString();

  const events = await RNCalendarEvents.fetchAllEvents(startDate, endDate);
  events.forEach((event) => {
    console.log('事件:', event.title, '开始:', event.startDate);
  });
  return events;
};

saveEvent - 创建/更新事件

创建新事件或更新已有事件。如果 details 中包含 id,则更新该事件;否则创建新事件。

类型(title: string, details?: EventDetails, options?: SaveEventOptions) => Promise<string>

参数

title: 事件标题

details: 事件详情

属性 类型 必填 说明
id string 事件 ID(更新时)
type number 事件类型
title string 事件标题
location object 地点信息
startTime number 开始时间(毫秒)
endTime number 结束时间(毫秒)
isAllDay boolean 是否全天事件
attendee array 参与者列表
timeZone string 时区
reminderTime number[] 提醒时间列表
recurrenceRule object 重复规则
description string 事件描述

location 对象

属性 类型 说明
location string 地点名称
longitude number 经度
latitude number 纬度

recurrenceRule 对象

属性 类型 说明
recurrenceFrequency number 重复频率(1=每日,2=每周等)
expire number 过期时间(0表示永不过期)

返回值:事件的 ID

tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const createEvent = async () => {
  const now = Date.now();
  const eventId = await RNCalendarEvents.saveEvent('项目会议', {
    title: '项目会议',
    startTime: now + 60 * 60 * 1000,
    endTime: now + 2 * 60 * 60 * 1000,
    location: {
      location: '会议室A',
      longitude: 116.397,
      latitude: 39.908,
    },
    description: '讨论项目进度',
    isAllDay: false,
    reminderTime: [now + 30 * 60 * 1000],
  });
  console.log('创建事件成功, ID:', eventId);
  return eventId;
};

const updateEvent = async (eventId: string) => {
  const now = Date.now();
  const newEventId = await RNCalendarEvents.saveEvent('项目会议(已更新)', {
    id: eventId,
    title: '项目会议(已更新)',
    startTime: now + 2 * 60 * 60 * 1000,
    endTime: now + 3 * 60 * 60 * 1000,
    description: '会议时间已调整',
  });
  console.log('更新事件成功');
  return newEventId;
};

removeEvent - 删除事件

根据 ID 删除指定的事件。

类型(id: string, options?: { futureEvents?: boolean }) => Promise<boolean>

参数

参数 类型 必填 说明
id string 事件 ID
futureEvents boolean 是否删除未来的重复事件

返回值:是否删除成功

tsx 复制代码
import RNCalendarEvents from 'react-native-calendar-events';

const deleteEvent = async (eventId: string) => {
  const success = await RNCalendarEvents.removeEvent(eventId);
  console.log('删除事件:', success ? '成功' : '失败');
  return success;
};

📋 完整示例

ts 复制代码
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  StyleSheet,
  ScrollView,
  TouchableOpacity,
  SafeAreaView,
  StatusBar,
  Alert,
  TextInput,
  Switch,
  TouchableWithoutFeedback,
} from 'react-native';
import RNCalendarEvents from 'react-native-calendar-events';

interface Calendar {
  id: string;
  title: string;
  type?: string;
  displayName?: string;
}

interface Event {
  id: string;
  title: string;
  startDate: string;
  endDate?: string;
  location?: string;
  description?: string;
  allDay?: boolean;
}

const App: React.FC = () => {
  const [calendars, setCalendars] = useState<Calendar[]>([]);
  const [events, setEvents] = useState<Event[]>([]);
  const [isAuthorized, setIsAuthorized] = useState(false);
  const [showForm, setShowForm] = useState(false);
  const [eventTitle, setEventTitle] = useState('');
  const [eventDescription, setEventDescription] = useState('');
  const [eventLocation, setEventLocation] = useState('');
  const [isAllDay, setIsAllDay] = useState(false);

  useEffect(() => {
    checkPermission();
  }, []);

  const checkPermission = async () => {
    const status = await RNCalendarEvents.checkPermissions();
    setIsAuthorized(status === 'authorized');
    if (status === 'authorized') {
      loadCalendars();
      loadEvents();
    }
  };

  const requestPermission = async () => {
    const status = await RNCalendarEvents.requestPermissions();
    setIsAuthorized(status === 'authorized');
    if (status === 'authorized') {
      loadCalendars();
      loadEvents();
    } else {
      Alert.alert('权限被拒绝', '请在设置中允许访问日历');
    }
  };

  const loadCalendars = async () => {
    try {
      const calendarList = await RNCalendarEvents.findCalendars();
      setCalendars(calendarList);
    } catch (error) {
      console.error('获取日历失败:', error);
    }
  };

  const loadEvents = async () => {
    try {
      const now = new Date();
      const startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString();
      const endDate = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000).toISOString();
      const eventList = await RNCalendarEvents.fetchAllEvents(startDate, endDate);
      setEvents(eventList);
    } catch (error) {
      console.error('获取事件失败:', error);
    }
  };

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

    try {
      const now = new Date();
      let startDate: string;
      let endDate: string;

      if (isAllDay) {
        const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
        startDate = today.toISOString();
        endDate = tomorrow.toISOString();
      } else {
        startDate = new Date(now.getTime() + 60 * 60 * 1000).toISOString();
        endDate = new Date(now.getTime() + 2 * 60 * 60 * 1000).toISOString();
      }

      const eventId = await RNCalendarEvents.saveEvent(eventTitle, {
        startDate,
        endDate,
        location: eventLocation || undefined,
        description: eventDescription,
        allDay: isAllDay,
      });

      Alert.alert('成功', '事件创建成功');
      setShowForm(false);
      setEventTitle('');
      setEventDescription('');
      setEventLocation('');
      setIsAllDay(false);
      loadEvents();
    } catch (error) {
      console.error('创建事件失败:', error);
      Alert.alert('错误', '创建事件失败');
    }
  };

  const deleteEvent = async (eventId: string) => {
    Alert.alert('确认删除', '确定要删除这个事件吗?', [
      { text: '取消', style: 'cancel' },
      {
        text: '删除',
        style: 'destructive',
        onPress: async () => {
          try {
            await RNCalendarEvents.removeEvent(eventId);
            Alert.alert('成功', '事件已删除');
            loadEvents();
          } catch (error) {
            console.error('删除事件失败:', error);
            Alert.alert('错误', '删除事件失败');
          }
        },
      },
    ]);
  };

  const formatDate = (dateString: string) => {
    try {
      const date = new Date(dateString);
      if (isNaN(date.getTime())) {
        return dateString;
      }
      const year = date.getUTCFullYear();
      const month = String(date.getUTCMonth() + 1).padStart(2, '0');
      const day = String(date.getUTCDate()).padStart(2, '0');
      const hours = String(date.getUTCHours()).padStart(2, '0');
      const minutes = String(date.getUTCMinutes()).padStart(2, '0');
      return `${year}-${month}-${day} ${hours}:${minutes}`;
    } catch {
      return dateString;
    }
  };

  const getLocationText = (location: Event['location']) => {
    return location || '';
  };

  if (!isAuthorized) {
    return (
      <SafeAreaView style={styles.container}>
        <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
        <View style={styles.permissionContainer}>
          <Text style={styles.permissionTitle}>日历权限</Text>
          <Text style={styles.permissionText}>
            需要日历权限才能管理您的日程事件
          </Text>
          <TouchableOpacity style={styles.permissionButton} onPress={requestPermission}>
            <Text style={styles.permissionButtonText}>授权访问日历</Text>
          </TouchableOpacity>
        </View>
      </SafeAreaView>
    );
  }

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" backgroundColor="#FFFFFF" />
      <View style={styles.header}>
        <Text style={styles.headerTitle}>日历事件管理</Text>
        <TouchableOpacity
          style={styles.addButton}
          onPress={() => setShowForm(true)}
        >
          <Text style={styles.addButtonText}>+ 新建</Text>
        </TouchableOpacity>
      </View>

      <ScrollView style={styles.content}>
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>日历列表 ({calendars.length})</Text>
          {calendars.length === 0 ? (
            <Text style={styles.emptyText}>暂无日历</Text>
          ) : (
            calendars.map((calendar, index) => (
              <View key={calendar.id || index} style={styles.calendarItem}>
                <View style={styles.calendarColor} />
                <View style={styles.calendarInfo}>
                  <Text style={styles.calendarTitle}>{calendar.title || '未命名日历'}</Text>
                  <Text style={styles.calendarType}>类型: {calendar.type || '本地'}</Text>
                </View>
              </View>
            ))
          )}
        </View>

        <View style={styles.section}>
          <Text style={styles.sectionTitle}>近期事件 ({events.length})</Text>
          {events.length === 0 ? (
            <View style={styles.emptyContainer}>
              <Text style={styles.emptyText}>暂无事件</Text>
              <Text style={styles.emptyHint}>点击右上角"新建"创建事件</Text>
            </View>
          ) : (
            events.map((event) => (
              <View key={event.id} style={styles.eventItem}>
                <View style={styles.eventHeader}>
                  <Text style={styles.eventTitle}>{event.title}</Text>
                  <TouchableOpacity onPress={() => deleteEvent(event.id)}>
                    <Text style={styles.deleteText}>删除</Text>
                  </TouchableOpacity>
                </View>
                <Text style={styles.eventTime}>开始: {formatDate(event.startDate)}</Text>
                {event.endDate && (
                  <Text style={styles.eventTime}>结束: {formatDate(event.endDate)}</Text>
                )}
                {event.location && (
                  <Text style={styles.eventLocation}>📍 {getLocationText(event.location)}</Text>
                )}
                {event.description && (
                  <Text style={styles.eventDescription}>{event.description}</Text>
                )}
              </View>
            ))
          )}
        </View>
      </ScrollView>

      {showForm && (
        <TouchableWithoutFeedback onPress={() => setShowForm(false)}>
          <View style={styles.overlay}>
            <TouchableWithoutFeedback onPress={() => {}}>
              <View style={styles.formContainer}>
                <Text style={styles.formTitle}>新建事件</Text>

                <Text style={styles.inputLabel}>事件标题</Text>
                <TextInput
                  style={styles.input}
                  placeholder="请输入事件标题"
                  value={eventTitle}
                  onChangeText={setEventTitle}
                />

                <Text style={styles.inputLabel}>地点(可选)</Text>
                <TextInput
                  style={styles.input}
                  placeholder="请输入地点"
                  value={eventLocation}
                  onChangeText={setEventLocation}
                />

                <Text style={styles.inputLabel}>描述(可选)</Text>
                <TextInput
                  style={[styles.input, styles.textArea]}
                  placeholder="请输入描述"
                  value={eventDescription}
                  onChangeText={setEventDescription}
                  multiline
                />

                <View style={styles.switchRow}>
                  <Text style={styles.switchLabel}>全天事件</Text>
                  <Switch value={isAllDay} onValueChange={setIsAllDay} />
                </View>

                <View style={styles.formButtons}>
                  <TouchableOpacity
                    style={[styles.formButton, styles.cancelButton]}
                    onPress={() => setShowForm(false)}
                  >
                    <Text style={styles.cancelButtonText}>取消</Text>
                  </TouchableOpacity>
                  <TouchableOpacity
                    style={[styles.formButton, styles.confirmButton]}
                    onPress={createEvent}
                  >
                    <Text style={styles.confirmButtonText}>创建</Text>
                  </TouchableOpacity>
                </View>
              </View>
            </TouchableWithoutFeedback>
          </View>
        </TouchableWithoutFeedback>
      )}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  permissionContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  permissionTitle: {
    fontSize: 24,
    fontWeight: '700',
    color: '#333333',
    marginBottom: 12,
  },
  permissionText: {
    fontSize: 16,
    color: '#666666',
    textAlign: 'center',
    marginBottom: 24,
  },
  permissionButton: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 32,
    paddingVertical: 14,
    borderRadius: 8,
  },
  permissionButtonText: {
    color: '#FFFFFF',
    fontSize: 16,
    fontWeight: '600',
  },
  header: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: 16,
    backgroundColor: '#FFFFFF',
    borderBottomWidth: 1,
    borderBottomColor: '#E5E5EA',
  },
  headerTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#333333',
  },
  addButton: {
    backgroundColor: '#007AFF',
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 6,
  },
  addButtonText: {
    color: '#FFFFFF',
    fontSize: 14,
    fontWeight: '600',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  section: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#333333',
    marginBottom: 12,
  },
  calendarItem: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#F0F0F0',
  },
  calendarColor: {
    width: 12,
    height: 12,
    borderRadius: 6,
    backgroundColor: '#007AFF',
    marginRight: 12,
  },
  calendarInfo: {
    flex: 1,
  },
  calendarTitle: {
    fontSize: 16,
    color: '#333333',
    fontWeight: '500',
  },
  calendarType: {
    fontSize: 14,
    color: '#999999',
    marginTop: 2,
  },
  emptyContainer: {
    alignItems: 'center',
    paddingVertical: 32,
  },
  emptyText: {
    fontSize: 16,
    color: '#999999',
  },
  emptyHint: {
    fontSize: 14,
    color: '#CCCCCC',
    marginTop: 8,
  },
  eventItem: {
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#F0F0F0',
  },
  eventHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  eventTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#333333',
    flex: 1,
  },
  deleteText: {
    fontSize: 14,
    color: '#FF3B30',
    marginLeft: 12,
  },
  eventTime: {
    fontSize: 14,
    color: '#666666',
    marginTop: 4,
  },
  eventLocation: {
    fontSize: 14,
    color: '#666666',
    marginTop: 4,
  },
  eventDescription: {
    fontSize: 14,
    color: '#999999',
    marginTop: 4,
  },
  overlay: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    backgroundColor: 'rgba(0,0,0,0.5)',
    justifyContent: 'center',
    alignItems: 'center',
  },
  formContainer: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    padding: 20,
    width: '90%',
    maxWidth: 400,
  },
  formTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#333333',
    marginBottom: 20,
    textAlign: 'center',
  },
  inputLabel: {
    fontSize: 14,
    color: '#666666',
    marginBottom: 4,
  },
  input: {
    borderWidth: 1,
    borderColor: '#E5E5EA',
    borderRadius: 8,
    paddingHorizontal: 16,
    paddingVertical: 12,
    fontSize: 16,
    marginBottom: 12,
  },
  textArea: {
    height: 80,
    textAlignVertical: 'top',
  },
  switchRow: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    paddingVertical: 8,
    marginBottom: 20,
  },
  switchLabel: {
    fontSize: 16,
    color: '#333333',
  },
  formButtons: {
    flexDirection: 'row',
    gap: 12,
  },
  formButton: {
    flex: 1,
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
  },
  cancelButton: {
    backgroundColor: '#F0F0F0',
  },
  cancelButtonText: {
    fontSize: 16,
    color: '#666666',
    fontWeight: '600',
  },
  confirmButton: {
    backgroundColor: '#007AFF',
  },
  confirmButtonText: {
    fontSize: 16,
    color: '#FFFFFF',
    fontWeight: '600',
  },
});

export default App;

⚠️ 注意事项

1. 权限配置

  • 必须在 module.json5 中配置 READ_CALENDARWRITE_CALENDAR 权限
  • 权限说明文案需要在 string.json 中定义
  • 首次使用时需要调用 requestPermissions() 请求权限

2. 时间格式

  • fetchAllEvents 的参数需要使用时间戳字符串
  • saveEventstartTimeendTime 使用毫秒时间戳
  • 时间戳需要转换为字符串传递

3. 日历 ID

  • 创建事件前需要确保有可用的日历
  • 可以通过 findCalendars() 获取日历列表
  • 建议使用系统默认日历或创建专用日历

4. 事件更新

  • 更新事件时需要在 details 中传入事件 ID
  • 更新会覆盖原有的事件信息
  • 建议先查询事件详情再进行更新

5. 重复事件

  • recurrenceFrequency 值:1=每日,2=每周,3=每月,4=每年
  • expire 为 0 表示永不过期
  • 删除重复事件时可以指定 futureEvents 参数

6. 错误处理

  • 所有 API 都返回 Promise,需要使用 try-catch 捕获错误
  • 权限被拒绝时需要引导用户到设置中开启
  • 日历操作失败时需要给用户明确的错误提示

7. 性能优化

  • 查询事件时指定合理的时间范围
  • 避免频繁查询大量事件
  • 建议缓存日历列表减少查询次数
相关推荐
837927397@QQ.COM2 小时前
个人理解无界原理
开发语言·前端·javascript
冰暮流星2 小时前
javascript之Dom查询操作1
java·前端·javascript
Cxiaomu3 小时前
像ChatGPT一样逐字输出:React + TypeScript 流式接收与“打字机”效果实现方案
人工智能·react.js·chatgpt·typescript
取码网3 小时前
2025最新口红机防篡改版本源码
android·java·javascript
努力的lpp3 小时前
【小迪安全第14天:前端JS架构信息打点与API接口枚举】
前端·javascript·安全
南风知我意9574 小时前
Map 与 WeakMap 深度解析:从内存泄漏到 Vue 3 响应式原理的完整指南
前端·javascript·vue.js
github_czy4 小时前
Vue 3 组件生命周期
前端·javascript·vue.js
越甲八千4 小时前
Vue3启动流程和文件结构
前端·javascript·vue.js