集光 - 智能笔记应用

📱 应用简介

集光是一款专为HarmonyOS生态设计的智能笔记应用,提供安全、高效、美观的笔记管理体验。应用集成了华为账号服务,支持多种笔记编辑功能,让您的创意和想法得到完美记录。

✨ 核心特性

🔐 安全可靠

  • 生物识别解锁:支持指纹、面部识别等生物认证
  • 手势密码:自定义手势密码保护隐私内容
  • 应用锁:为整个应用提供额外的安全保护

📝 强大编辑

  • 富文本编辑:支持文字样式、颜色、大小等格式设置
  • 多媒体支持:插入图片、截图,丰富笔记内容
  • 智能排版:自动优化文本布局,提升阅读体验

🗂️ 智能管理

  • 分类管理:创建、编辑、删除笔记分类
  • 搜索功能:快速定位所需笔记内容
  • 多种视图:列表视图和卡片视图自由切换
  • 排序方式:按创建时间、修改时间灵活排序

🌙 个性化体验

  • 夜间模式:护眼模式,适合夜间使用
  • 主题切换:支持明暗主题自动切换
  • 多选操作:批量管理笔记,提高效率

🏗️ 技术架构

模块化设计

集光
├── 主应用模块 (product/phone)
├── 公共组件 (components)
│   ├── 应用密码设置 (secretlock)
│   ├── 富文本编辑器 (richeditor)
│   ├── 用户登录 (login)
│   ├── 意见反馈 (feed_back)
│   └── 应用更新检测 (check_app_update)
└── 公共工具 (common)
    ├── 数据源管理 (datasource)
    └── 工具类库 (utils)

核心技术

  • HarmonyOS 5.0+:原生HarmonyOS应用开发
  • ArkTS:现代化的应用开发语言
  • Stage模型:新一代应用开发模型

🚀 快速开始

环境要求

  • DevEco Studio 5.0.3 Release 及以上
  • HarmonyOS SDK 5.0.3 Release 及以上
  • 支持设备:华为手机(包括双折叠和阔折叠)
  • 系统版本:HarmonyOS 5.0.3(15) 及以上

安装配置

  1. 克隆项目

    git clone [项目地址]
    cd JiGaung
    
  2. 配置华为账号服务

    • 在AppGallery Connect创建应用
    • 配置Client ID到product/phone/src/main/module.json5
    • 申请quickLoginMobilePhone权限
  3. 应用签名

    • 进行手工签名配置
    • 添加证书公钥指纹
  4. 运行应用

    • 连接调试设备
    • 选择"Run > Run ‘phone’“或"Run > Debug ‘phone’”

📱 主要功能

首页功能

  • 笔记展示:支持列表和卡片两种展示模式
  • 分类管理:创建、编辑、删除笔记分类
  • 搜索笔记:快速查找笔记内容
  • 多选操作:长按多选,批量管理
  • 排序功能:按时间排序,方便查找

笔记编辑

  • 富文本编辑:支持文字格式、颜色、大小设置
  • 图片插入:支持本地图片和截图插入
  • 撤销重做:操作历史记录,支持撤销重做
  • 内容分享:支持笔记内容分享到其他应用
  • 自动保存:实时保存,防止内容丢失

个人中心

  • 用户信息:华为账号一键登录,头像昵称管理
  • 回收站:删除笔记恢复,彻底删除功能
  • 隐私设置:生物识别、手势密码等安全设置
  • 应用设置:夜间模式、通知设置、版本检测
  • 意见反馈:问题反馈,帮助改进应用

🔧 开发指南

组件使用

如需单独使用某个组件,请参考对应组件的使用指导:

组件 描述 使用指导
secretlock 应用密码设置 使用指导
richeditor 富文本编辑器 使用指导
login 用户登录 使用指导
feed_back 意见反馈 使用指导

自定义开发

  • 修改AppScope/app.json5中的bundleName
  • 调整AppScope/resources/base/element/string.json中的应用名称
  • 根据需要修改主题色彩和样式

🧩 系统设计与实现说明

本节面向希望深入学习和二开的开发者,按“从需求到实现”的顺序介绍集光的整体架构、核心业务流程以及关键模块的实现思路。

1. 整体架构概览

  • UIAbility 入口层

    • EntryAbility.ets 作为应用入口,负责:
      • 初始化主题控制器 ThemeController,同步系统深浅色模式;
      • 初始化窗口尺寸,写入 AppStorage,供 UI 自适配(含折叠屏断点适配 WindowUtil.registerBreakPoint);
      • 初始化数据库:调用 DatabaseManager.instance.initDatabase() 创建和迁移数据表;
      • 配置状态栏、导航栏、通知授权等系统级能力。
  • 页面与导航层

    • MainPage.ets 作为主页面,使用 Tabs + Navigation 构建四个核心 Tab:
      • PictureView:图片集
      • NotesView:笔记集(本项目核心)
      • ToDoView:待办集
      • MineView:我的
    • 通过 NavPathStack 实现页面栈导航(如从 NotesView 跳转到 EditNotes)。
  • 业务页面层

    • NotesView.ets:笔记列表页,负责分类筛选、多选操作、置顶、排序等;
    • EditNotes.ets:笔记编辑页,集成富文本编辑器、背景色、分享/复制等能力;
    • 其它如 EditCategory.etsSearchPage.ets 等负责辅助业务。
  • 组件与服务层

    • 公共组件在 components/ 下:
      • richeditor:富文本编辑器组件与相关数据模型;
      • secretlock:手势锁、应用锁逻辑;
      • login:华为账号登录;
      • feed_back:意见反馈;
      • check_app_update:检查应用更新。
    • 公共工具在 common/
      • datasource:数据库访问、实体与服务 NoteService / CategoryService 等;
      • utilsGlobalInfoModelAppUtilWindowUtil 等工具与全局状态。
  • 数据存储层

    • 使用 @ohos.data.relationalStore 构建 notes.db
    • DatabaseManager.ets 负责数据库初始化、迁移和默认数据插入。

整体上可理解为:

UIAbility(入口) → MainPage(Tab 容器) → 各业务页面(Notes / EditNotes 等) → 业务服务层(NoteService / CategoryService) → DatabaseManager(RdbStore)

2. 数据模型与数据库设计

数据库通过 DatabaseManager 管理,使用 RDB 存储数据:

  • 数据库配置

    • 名称:notes.db
    • 安全级别:SecurityLevel.S1
  • 主要表结构

    • categories 表(分类):
      • id TEXT PRIMARY KEY:分类 ID(含特殊 ID:-1 = 全部笔记,-2 = 未分类笔记);
      • name TEXT:分类名称;
      • totalCount INTEGER:该分类下笔记数量,用于快速展示计数。
    • notes 表(笔记):
      • id TEXT PRIMARY KEY:笔记 ID;
      • title TEXT:标题;
      • categoryId TEXT:所属分类 ID;
      • content TEXT:纯文本内容;
      • styledContent TEXT:富文本内容(通过 StyleSerializer 序列化的 JSON 字符串);
      • description TEXT:用于卡片预览的摘要;
      • createTime / updateTime TEXT:创建/更新时间;
      • isPinned INTEGER:是否置顶;
      • backgroundColor TEXT:笔记卡片/详情背景颜色;
      • importance INTEGER:重要度等级;
      • isDeleted INTEGER:是否进入回收站。
  • 自动迁移逻辑

    • DatabaseManager.migrateDatabase() 使用 PRAGMA table_info(notes) 查询列信息;
    • 若缺少某个列(如 isPinned / backgroundColor / importance / styledContent / isDeleted),则通过 ALTER TABLE 动态新增;
    • 这样既支持老版本平滑升级,又避免手工 SQL 迁移出错。
  • 默认分类初始化

    • initDefaultCategories() 保证:
      • 若不存在 id = '-1',则插入“全部笔记”;
      • 若不存在 id = '-2',则插入“未分类笔记”。

关键实现示例(节选自 DatabaseManager.ets):

export class DatabaseManager {
  private static _instance: DatabaseManager;
  private rdbStore: relationalStore.RdbStore | null = null;
  private readonly STORE_CONFIG: relationalStore.StoreConfig = {
    name: 'notes.db',
    securityLevel: relationalStore.SecurityLevel.S1
  };

  async initDatabase(): Promise<void> {
    const context = getContext(this);
    this.rdbStore = await relationalStore.getRdbStore(context, this.STORE_CONFIG);
    await this.createTables();
    await this.migrateDatabase();
  }

  private async createTables(): Promise<void> {
    if (!this.rdbStore) return;

    const categoryTableSql = `
      CREATE TABLE IF NOT EXISTS categories (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        totalCount INTEGER DEFAULT 0
      )
    `;

    const noteTableSql = `
      CREATE TABLE IF NOT EXISTS notes (
        id TEXT PRIMARY KEY,
        title TEXT,
        categoryId TEXT,
        content TEXT,
        styledContent TEXT,
        description TEXT,
        createTime TEXT,
        updateTime TEXT,
        isPinned INTEGER DEFAULT 0,
        backgroundColor TEXT DEFAULT "#fefefe",
        importance INTEGER DEFAULT 0,
        isDeleted INTEGER DEFAULT 0,
        FOREIGN KEY (categoryId) REFERENCES categories (id)
      )
    `;

    await this.rdbStore.executeSql(categoryTableSql);
    await this.rdbStore.executeSql(noteTableSql);
    await this.migrateDatabase();
    await this.initDefaultCategories();
  }

  private async migrateDatabase(): Promise<void> {
    if (!this.rdbStore) return;

    const resultSet = await this.rdbStore.querySql('PRAGMA table_info(notes)');
    let hasIsPinnedColumn = false;
    // 省略其它列检查...

    if (resultSet && resultSet.rowCount > 0) {
      while (await resultSet.goToNextRow()) {
        const columnName = await resultSet.getString(1);
        if (columnName === 'isPinned') {
          hasIsPinnedColumn = true;
        }
        // 根据实际名称检查 backgroundColor / importance / styledContent / isDeleted 等
      }
    }

    if (!hasIsPinnedColumn) {
      await this.rdbStore.executeSql('ALTER TABLE notes ADD COLUMN isPinned INTEGER DEFAULT 0');
    }
    // 同理为其它字段做 ALTER TABLE
  }

  private async initDefaultCategories(): Promise<void> {
    if (!this.rdbStore) return;

    const allNotesCountResult = await this.rdbStore
      .querySql('SELECT COUNT(*) as count FROM categories WHERE id = ?', ['-1']);
    // 若不存在“全部笔记”,则插入默认分类
    // ...
  }
}

3. 首页与笔记列表(NotesView)实现思路

3.1 MainPage:Tab 容器
  • 使用 Tabs + TabContent 实现四个子页面:图片集、笔记集、待办集、我的;
  • 自定义 tabBarBuilder 绘制底部图标和文字;
  • 通过 curIndex 控制当前选中 Tab;
  • 使用 GlobalInfoModel 与各页面共享刷新方法,例如:
    • 当切换到“笔记集”Tab 时,调用 globalInfo.refreshNotesView() 强制 NotesView 刷新数据;
    • 当切换到“图片集”Tab 时,调用 globalInfo.refreshPictureView() 刷新图片数据。

这种设计的好处是:Tab 之间无需强耦合,统一通过 GlobalInfoModel 进行“跨页面刷新”通信。

3.2 NotesView:笔记列表核心逻辑

NotesView.ets 是笔记模块的核心列表页面,主要职责:

  • 业务服务依赖

    • NoteService:提供笔记增删改查;
    • CategoryService:管理分类与计数;
    • SortController:管理排序字段与顺序;
    • NoteSearchController:管理搜索关键字与结果;
    • SelectedController:管理多选状态;
    • SettingController:应用设置相关(如通知授权)。
  • 本地状态管理

    • @Local noteList: Note[]:当前列表中的笔记;
    • @Local dataList: LazyDataSource<Note>:用于懒加载/瀑布流展示的数据源;
    • @Local categoryList: Array<Category>:分类列表;
    • @Local showListType: SHOW_METHOD_ENUM:列表/卡片等展示方式。
  • 生命周期逻辑(aboutToAppear)

    • 重置多选状态:selectedController.recoverInitState()
    • 若开启手势锁,则跳转至 DrawLock 页面;
    • 重置搜索关键字,获取默认分类 getFirstCategory()
    • 根据当前分类、排序、搜索条件获取笔记:noteService.getNoteList(...)
    • 初始化所有笔记的选中状态:noteService.initSelectedState()
    • 遍历所有分类并统计每个分类的笔记数量:refreshAllCategoryNoteCounts
    • 调用 updateDataList() 同步 noteListdataList
    • refreshNotesView 方法挂到 GlobalInfoModel,供外部刷新调用。
  • 刷新逻辑(refreshData)

    • 重新读取当前分类下笔记并更新 noteList
    • 若不在多选模式,重置选中状态;
    • 调用 forceRefreshUI() 清空并重建 dataList,通过 notifyDataChange 触发 UI 刷新;
    • 重新统计分类计数,保证分类弹窗中的数量与实际一致。
  • 多选与置顶等交互

    • 通过 SelectedController 记录选中 ID 集合与计数;
    • 长按进入多选模式:handleLangPress
    • “全选”、“取消全选”、批量删除/移动/置顶都基于 selectedIds 实现。

关键实现示例

MainPage:Tab 切换与跨页面刷新(节选自 MainPage.ets

@Entry
@ComponentV2
struct MainPage {
  @Local curIndex: number = 0;
  @Provider('appPathStack') appPathStack: NavPathStack = new NavPathStack();
  @Local globalInfo: GlobalInfoModel = AppStorageV2
    .connect<GlobalInfoModel>(GlobalInfoModel, () => new GlobalInfoModel())!;

  build() {
    Navigation(this.appPathStack) {
      Column() {
        Tabs({ barPosition: BarPosition.End, index: this.curIndex }) {
          TabContent() {
            PictureView();
          }.tabBar(this.tabBarBuilder(0));

          TabContent() {
            NotesView();
          }.tabBar(this.tabBarBuilder(1));

          TabContent() {
            ToDoView();
          }.tabBar(this.tabBarBuilder(2));

          TabContent() {
            MineView();
          }.tabBar(this.tabBarBuilder(3));
        }
        .onChange((index: number) => {
          this.curIndex = index;
          if (index === 1 && this.globalInfo.refreshNotesView) {
            setTimeout(() => this.globalInfo.refreshNotesView &&
              this.globalInfo.refreshNotesView(), 100);
          }
          if (index === 0 && this.globalInfo.refreshPictureView) {
            setTimeout(() => this.globalInfo.refreshPictureView &&
              this.globalInfo.refreshPictureView(), 100);
          }
        })
      }
    }
  }
}

NotesView:初始化与刷新逻辑(节选自 NotesView.ets

@ComponentV2
export struct NotesView {
  noteService: NoteService = NoteService.instance;
  categoryService: CategoryService = CategoryService.instance;
  // ... 省略其它依赖

  @Local noteList: Note[] = [];
  @Local dataList: LazyDataSource<Note> = new LazyDataSource();

  async aboutToAppear(): Promise<void> {
    this.selectedController.recoverInitState();

    if (this.secretLock.gesture) {
      const params: Record<string, Object> =
        { 'fromEntrance': true, 'appPathStack': this.appPathStack };
      this.appPathStack.pushPathByName('DrawLock', params);
    }

    this.noteSearchController.searchKeyword = '';
    this.currentCategory = await this.categoryService.getFirstCategory();
    this.noteList = await this.noteService.getNoteList(
      this.currentCategory.id,
      this.sortController.sortBy,
      this.noteSearchController.searchKeyword
    );

    this.noteService.initSelectedState();

    const allNotes = await this.noteService.getNoteList('-1');
    const categoryNotesMap = new Map<string, Note[]>();
    for (const category of await this.categoryService.getCategoryList()) {
      if (category.id !== '-1') {
        const categoryNotes = await this.noteService.getNoteList(category.id);
        categoryNotesMap.set(category.id, categoryNotes);
      }
    }
    await this.categoryService.refreshAllCategoryNoteCounts(allNotes, categoryNotesMap);

    this.categoryList = await this.categoryService.getCategoryList();
    this.updateDataList();
    this.updateNoteCount();

    this.syncSelectedState();
    this.globalInfo.refreshNotesView = this.refreshData.bind(this);
  }

  async refreshData(): Promise<void> {
    this.noteList = await this.noteService.getNoteList(
      this.currentCategory.id,
      this.sortController.sortBy,
      this.noteSearchController.searchKeyword
    );

    if (!this.selectedController.isCtrl) {
      this.noteService.initSelectedState();
    }

    this.forceRefreshUI();
    this.updateNoteCount();
    this.syncSelectedState();
  }

  private forceRefreshUI(): void {
    this.dataList.clear();
    this.dataList.notifyDataChange(0);
    setTimeout(() => {
      this.noteList.forEach((item: Note) => this.dataList.pushData(item));
      this.dataList.notifyDataChange(0);
    }, 0);
  }
}

4. 笔记编辑(EditNotes + 富文本编辑器)实现思路

4.1 富文本编辑器组件
  • 富文本编辑器封装在 components/richeditor 中,对外通过 Index.ets 提供:

    • RichEditorController:编辑器控制器,负责光标、选区、历史记录(撤销/重做)和当前样式状态;
    • RichEditorArea:真正的富文本输入区域组件;
    • Note / LazyDataSource<Note> 等数据模型;
    • StyleSerializer:负责将样式(加粗、斜体、下划线、阴影、对齐方式等)序列化到字符串,并在读取时反序列化回枚举和结构体。
  • 样式持久化:

    • 编辑器内部维护 MutableStyledString,记录每段文本的样式;
    • 保存时将 MutableStyledString 通过 StyleSerializer 转为 JSON 字符串,存入 notes.styledContent
    • 读取时反序列化回 MutableStyledString,然后调用 RichEditorController.restoreStateFromStyledString 恢复光标位置、当前样式按钮状态等。
4.2 EditNotes 页面结构

EditNotes.ets 是笔记编辑的核心页面,关键成员包括:

  • 核心依赖

    • RichEditorController:单例控制器,跨页面持有编辑状态;
    • SnapShotController:用于对当前笔记区域截图分享;
    • NoteService:保存/更新笔记数据;
    • ThemeController:感知深浅色模式;
    • GlobalInfoModel:用于在保存笔记后通知 NotesView 刷新数据。
  • 本地状态

    • @Local currentNote: Note:当前正在编辑的笔记对象;
    • @Local noteTitle: string:标题输入内容;
    • @Local selectedBackgroundColor: string:当前笔记背景色;
    • @Local lightModeColors[] / darkModeColors[]:深浅色模式下可选背景色列表;
    • @Local isEditNote: boolean:标记是“新建”还是“编辑已有笔记”。
  • 键盘与主题适配

    • aboutToAppear 中将 KeyboardAvoidMode 设置为 RESIZE_WITH_CARET,避免软键盘遮挡输入区域;
    • 根据 ThemeController.currentColorMode 设置默认背景色;
    • 启动定时器轮询主题变化,若系统从浅色切到深色,会自动切换一组更适合阅读的背景色。
  • 工具栏与更多操作

    • toolBar() 中提供:撤销/重做/保存 等基础操作;
    • moreFunctionMenu() 中提供:
      • 分享:通过 SnapShotController.onceSnapshot() 截图当前笔记区域;
      • 复制:通过 UnifiedData + SystemPasteboard 将富文本转为纯文本复制到系统剪贴板。
  • 保存流程(简化版)

    1. 用户点击工具栏中的“保存”图标;
    2. 调用 saveNote()
      • 将编辑器中的内容序列化为 content / styledContent / description
      • 若为新笔记,生成 idcreateTime 并插入数据库;
      • 若为编辑已有笔记,更新数据库记录(包括 description,确保列表卡片实时更新摘要);
    3. 保存成功后:
      • 关闭键盘与编辑状态:controller.stopEditing()
      • RichEditorController.showMoreFunction 设置为 true,显示更多功能菜单;
      • 通知 NotesView 刷新列表;
      • 结束编辑状态并返回。

关键实现示例(节选自 EditNotes.ets

@ComponentV2
struct EditNotes {
  richEditorController: RichEditorController = RichEditorController.instance;
  noteService: NoteService = NoteService.instance;
  @Local currentNote: Note = new Note(new MutableStyledString(''));
  @Local noteTitle: string = '';
  @Local isEditNote: boolean = false;
  @Local selectedBackgroundColor: string = '#fefefe';

  async saveNote(): Promise<boolean> {
    let styledString = this.richEditorController.controller.getStyledString();
    let content = styledString.getString();

    // 新建且内容和标题都为空时不保存
    if (content === '' && this.noteTitle === '') {
      return this.isEditNote ? false : true;
    }

    if (this.isEditNote) {
      this.currentNote.updateContent(
        styledString,
        this.noteTitle,
        undefined,
        this.selectedBackgroundColor
      );
      await this.noteService.updateNote(this.currentNote);
    } else {
      this.currentNote.title = this.noteTitle;
      this.currentNote.styledString = styledString;
      this.currentNote.description = styledString.getString();
      this.currentNote.backgroundColor = this.selectedBackgroundColor;
      await this.noteService.addNote(this.currentNote);
      this.isEditNote = true;
    }

    if (this.globalInfo && this.globalInfo.refreshNotesView) {
      this.globalInfo.refreshNotesView();
    }

    return true;
  }

  build() {
    NavDestination() {
      Column() {
        RichEditorArea({
          noteTitle: this.currentNote.title,
          noteContent: this.currentNote.styledString,
          snapShotController: this.snapShotController,
          titleChange: (title: string) => {
            this.noteTitle = title;
          }
        })
      }
      .backgroundColor(this.selectedBackgroundColor);
    }
    .menus(this.toolBar());
  }
}

5. 安全与隐私(SecretLock 等)

  • SecretLock 组件负责应用级安全:

    • 手势密码解锁:在进入 NotesView 时,如果检测到已设置手势密码,则通过 appPathStack.pushPathByName('DrawLock', params) 先进入手势解锁页面;
    • 解锁成功后才允许继续访问笔记内容。
  • 生物识别解锁(指纹/人脸)也可以与 secretlock 联动,在应用启动或从后台回到前台时进行校验。

6. 典型业务流程串联

6.1 应用启动 → 显示主页
  1. 系统启动 EntryAbility
  2. onCreate 中初始化主题、数据库;
  3. onWindowStageCreate 中:
    • 注册折叠屏断点;
    • 设置状态栏/导航栏属性;
    • windowStage.loadContent('pages/MainPage') 加载 MainPage
  4. MainPage 构建 Tabs,默认显示第一个 Tab(图片集),用户可切换到“笔记集”。
6.2 进入笔记列表 NotesView
  1. 用户点击“笔记集”Tab,curIndex = 1
  2. NotesView.aboutToAppear 执行:
    • 若开启手势锁,则跳转到手势解锁页面;
    • 加载当前分类下的笔记列表;
    • 同步分类计数;
    • 初始化 dataList,触发 UI 渲染;
    • refreshNotesView 注册到全局,便于其它页面保存后刷新列表。
6.3 新建 / 编辑笔记
  1. NotesView 点击“新建笔记”或笔记卡片,导航到 EditNotes
  2. EditNotes.aboutToAppear:配置键盘避让、主题模式、背景色等;
  3. 用户使用富文本工具栏编辑内容(加粗、斜体、下划线、阴影、对齐方式等);
  4. 点击“保存”:
    • 将样式序列化为 styledContent
    • 写入/更新 notes 表中的记录;
    • 通知 NotesView 刷新列表;
    • 结束编辑状态并返回。

7. 二开与扩展建议

如果你希望在本项目基础上做二次开发,可以参考以下思路:

  • 增加字段

    • notes 表中新增字段(例如标签、提醒时间等);
    • DatabaseManager.migrateDatabase() 中按现有模式检测并 ALTER TABLE,保持向后兼容;
    • Note 模型与 NoteService 中补充对应字段的读写逻辑。
  • 扩展富文本能力

    • richeditor 组件中增加新的样式(例如高亮、引用块等);
    • 更新 StyleSerializer 的序列化/反序列化逻辑;
    • EditNotes 的工具栏中增加对应的按钮和交互。
  • 增加云同步/多端能力

    • NoteService 层增加与云端的同步逻辑(基于华为云或自建服务);
    • 建议保持本地 RDB 为“真源”,云端做备份与协同,避免弱网络导致编辑卡顿。
  • 自定义安全策略

    • 扩展 secretlock 支持更多解锁策略(如时间锁、地理位置锁等);
    • EntryAbility.onForeground / NotesView.aboutToAppear 中按需插入校验逻辑。

通过阅读本节并结合对应的源码文件(EntryAbility.etsMainPage.etsNotesView.etsEditNotes.etsDatabaseManager.etscomponents/richeditor 等),你可以较为系统地掌握集光项目的整体设计思路,并在此基础上快速完成功能扩展或二次开发。

📄 开源协议

本项目采用 Apache 2.0 开源协议,欢迎贡献代码和提出建议。


集光 - 让记录更智能,让创意更闪耀 ✨

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐