前面我们已经介绍了如何用 AI 生成一份“AI 赋能大学生全流程计划”。但计划生成只是第一步,真正困难的是:这些任务到底怎么落到每天?怎么避开课表?怎么提醒自己?怎么和手机日历打通?

所以我做了一个配套 App:AI赋能大学生全流程计划

这个 App 的目标不是做一个完整商业产品,而是练习如何用 React Native 实现一个带有以下能力的移动端工具:

  • 解析 Markdown 表格任务
  • 自动把任务排到未来 30 天
  • 避开课程和手动添加的固定任务
  • 设置本地提醒
  • 同步到手机系统日历

    技术栈主要使用:

    React Native
    Expo
    expo-calendar
    expo-notifications
    AsyncStorage
    

    一、创建 Expo 项目

    先创建一个 Expo 项目:

    npx create-expo-app ai-student-schedule-app
    cd ai-student-schedule-app

    安装需要的依赖:

    npm install @react-native-async-storage/async-storage
    npx expo install expo-calendar expo-notifications expo-status-bar

    项目结构可以整理成这样:

    src/
      components/
        Timeline.js
      data/
        sample.js
      domain/
        planImport.js
        scheduler.js
      services/
        calendar.js
        notifications.js
        storage.js
    App.js
    app.json

    二、设计任务数据结构

    这个 App 里主要有三类数据。

    第一类是 AI 计划任务:

    {
      id: "task-1",
      title: "整理高数错题",
      area: "学业提升",
      durationMinutes: 35,
      priority: 5,
      preferredDay: 0,
      window: "evening"
    }

    第二类是课程:

    {
      id: "course-1",
      title: "高等数学",
      weekday: 1,
      startTime: "08:00",
      endTime: "09:40",
      weekRule: "full16",
      location: "教学楼 A101"
    }

    第三类是手动任务:

    {
      id: "manual-1",
      title: "社团会议",
      date: "2026-04-27",
      startTime: "19:00",
      endTime: "20:00"
    }

    这里的 weekRule 用来处理大学课程常见的周次规则:

    first8:前 8 周
    second8:后 8 周
    full16:1-16 周

    三、解析 Markdown 任务表

    为了让 App 能导入外部生成的计划,可以约定一种简单的 Markdown 表格格式:

    | 天数 | 时段 | 任务 | 对应目标 | 优先级 | 时长 | 证据 |
    | --- | --- | --- | --- | --- | --- | --- |
    | 第1天 | 晚上 | 整理课程薄弱点 | 学业提升 | 必做 | 35 分钟 | 错题清单 |
    ...
    | 第30天 | 晚上 | 月度复盘并生成下一轮计划 | 月度复盘 | 必做 | 60 分钟 | 复盘表 |
    

    解析逻辑放在 src/domain/planImport.js 中,核心思路是:

    1. 先找 ```app-import 代码块
    2. 再解析 Markdown 表格
    3. 把中文字段转换成内部任务对象
    4. 把“第30天”转换成 preferredDay: 29

    示例逻辑:

    function parsePreferredDay(value) {
      const match = String(value).match(/第?\s*(\d+)\s*天/);
      if (!match) return 0;
      return Number(match[1]) - 1;
    }
    

    时段可以这样转换:

    function normalizeWindow(value) {
      if (/上午|早上/.test(value)) return "morning";
      if (/下午/.test(value)) return "afternoon";
      if (/晚上|晚间/.test(value)) return "evening";
      return "any";
    }
    

    四、实现自动排程

    排程逻辑放在 src/domain/scheduler.js。

    可以先定义每天可用的时间段:

    const WINDOWS = {
      morning: [["08:00", "12:00"]],
      afternoon: [["14:00", "18:00"]],
      evening: [["19:00", "22:30"]],
      any: [["08:00", "12:00"], ["14:00", "18:00"], ["19:00", "22:30"]]
    };
    

    排程时需要判断时间是否冲突:

    function overlaps(aStart, aEnd, bStart, bEnd) {
      return aStart < bEnd && bStart < aEnd;
    }
    

    自动任务排入时,要避开:

    课程时间
    手动添加的固定任务
    已经排进去的其他任务

    基本流程是:

    遍历 AI 任务
      找到它偏好的日期
      找到它偏好的时段
      检查该时段是否有空位
      如果冲突,就往后找下一个可用空位
      如果 30 天内都找不到,就放入未排入列表
    

    五、制作页面结构

    主界面可以分成四个 Tab:

    今日
    计划
    课表
    手动
    

    在 App.js 中定义:

    const TABS = [
      { id: "today", label: "今日" },
      { id: "plan", label: "计划" },
      { id: "courses", label: "课表" },
      { id: "manual", label: "手动" }
    ];

    “计划”页面负责粘贴任务表并导入。

    “课表”页面负责添加固定课程。

    “手动”页面负责添加某天某时段的固定事项。

    “今日”页面展示当天和接下来几天的排程结果。

    六、本地保存数据

    用 AsyncStorage 保存课程、手动任务、导入任务和完成状态。

    import AsyncStorage from "@react-native-async-storage/async-storage";
    
    export async function saveAppState(state) {
      await AsyncStorage.setItem("app-state", JSON.stringify(state));
    }
    
    export async function loadAppState(defaultState) {
      const raw = await AsyncStorage.getItem("app-state");
      return raw ? JSON.parse(raw) : defaultState;
    }
    

    这样关闭 App 后,已经导入的计划和课程不会丢失。

    七、添加本地提醒

    使用 expo-notifications。

    import * as Notifications from "expo-notifications";
    
    export async function scheduleTaskReminders(events) {
      await Notifications.requestPermissionsAsync();
    
      for (const event of events) {
        const triggerTime = new Date(`${event.date}T${event.startTime}:00`);
        triggerTime.setMinutes(triggerTime.getMinutes() - 5);
    
        await Notifications.scheduleNotificationAsync({
          content: {
            title: "任务提醒",
            body: event.title
          },
          trigger: triggerTime
        });
      }
    }
    

    这里设置的是任务开始前 5 分钟提醒。

    八、同步系统日历

    使用 expo-calendar 创建系统日历事件。

    import * as Calendar from "expo-calendar";
    
    export async function syncEventsToCalendar(events) {
      const { status } = await Calendar.requestCalendarPermissionsAsync();
      if (status !== "granted") {
        throw new Error("没有日历权限");
      }
    
      const calendars = await Calendar.getCalendarsAsync(Calendar.EntityTypes.EVENT);
      const calendarId = calendars[0]?.id;
    
      for (const event of events) {
        await Calendar.createEventAsync(calendarId, {
          title: event.title,
          startDate: new Date(`${event.date}T${event.startTime}:00`),
          endDate: new Date(`${event.date}T${event.endTime}:00`),
          notes: event.area
        });
      }
    }
    

    Android 真机测试时,需要在 app.json 中声明权限:

    {
      "android": {
        "permissions": [
          "READ_CALENDAR",
          "WRITE_CALENDAR",
          "POST_NOTIFICATIONS"
        ]
      }
    }
    

    九、打包 Android APK

    如果只调试,可以运行:

    npx expo start

    如果要生成 Android 安装包,可以使用本地构建或 EAS Build。

    本地构建方式:

    npx expo prebuild
    cd android
    gradlew assembleRelease
    

    生成的 APK 通常在:

    android/app/build/outputs/apk/release/
    

    总结

    这个 App 的开发重点不在复杂 UI,而在几个移动端开发常见能力:

    文本解析
    本地排程
    冲突检测
    本地存储
    消息提醒
    系统日历接入
    Android 打包

    通过这个项目,可以比较完整地练习 React Native 在真实工具类 App 中的开发流程。

    Logo

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

    更多推荐