OpenHarmony从零开发记事本:ArkTS状态管理与数据持久化实战
最近花了一个周末在 OpenHarmony 上从零写了一个记事本 APP。开始之前我以为这只是个“列表 输入框 本地存储”的小练习真动手以后才发现光是把 ArkTS 的状态管理、Stage 模型的生命周期和数据持久化串起来就够写一篇文章了。这篇把我踩过的坑、能直接抄的代码、以及为什么这样做一次性讲清楚。如果你正准备用 OpenHarmony 练手或者想把一个小工具搬到鸿蒙生态这篇应该对你有用。记事本是个很神奇的项目功能需求谁都能秒懂但实现起来几乎覆盖了移动应用开发的所有高频环节——页面跳转、列表渲染、输入交互、数据增删改查、前后台切换保存。拿它当 OpenHarmony 的入门项目性价比非常高。下面直接进入正题。1. 内容整体设计与思路拆解1.1 为什么拿记事本练手OpenHarmony 的应用开发方式和传统的 Android 开发、小程序开发都有一定交集但又有自己的一套规则。记事本这个场景特别适合练手主要有三个原因不依赖复杂硬件能力不需要蓝牙、摄像头、传感器这些外设调试成本低。核心功能足够清晰新建、编辑、删除、列表展示、本地持久化随便一个用户都能描述需求。能覆盖 OpenHarmony 应用开发的基本盘ArkUI 页面怎么写、组件怎么绑定数据、路由怎么跳、数据库怎么存、Ability 生命周期怎么处理。如果一上来就做一个带登录、推送、云同步的完整产品大概率会被工程框架绕晕。记事本可以让你先把“单个页面怎么跑通”“数据怎么存下来”这两个最基本的问题解决掉再谈扩展。1.2 功能边界够用不等于简陋很多人在写项目时会陷入一个误区功能越多越好。实际上第一次写 OpenHarmony 应用MVP最小可用产品反而是最合适的选择。我这个记事本第一版只做了这些事功能模块具体内容优先级笔记列表按更新时间倒序展示标题、摘要、时间必须做新建笔记从列表页跳转到空编辑页必须做编辑笔记修改标题和正文带自动保存必须做删除笔记长按列表项弹窗确认删除必须做本地搜索按标题和正文关键字过滤建议做置顶、标签、富文本第一版不做后续扩展这个边界看起来很“小”但已经能把 OpenHarmony 的 UIAbility、页面路由、状态管理、关系型数据库全部串起来了。先做透这些基础能力后面加功能只是堆组件和字段的问题。1.3 总体技术选型我选择的是 API 9 以上的 Stage 模型开发语言用 ArkTSUI 用 ArkUI 声明式写法数据存储用关系型数据库 RelationalStore。技术点选择理由开发语言ArkTSOpenHarmony 应用的主流语言基于 TypeScript 但更严格UI 框架ArkUI声明式 UI状态驱动页面刷新应用模型Stage 模型API 9 开始的主推模型代码结构清晰数据持久化RelationalStore结构化数据支持 SQL 查询后续扩展方便页面路由router简单直接适合轻量级页面跳转为什么不直接用 Preferences 存数据Preferences 本质是键值对适合存配置项比如“是否第一次启动”“主题色”。如果整个笔记列表序列化成一个 JSON 塞进去也能跑但一旦要做搜索、排序、按 id 更新某一条就要把整个列表读出来再写回去越到后面越别扭。关系型数据库对这类场景才是正解。2. 核心细节解析与实操要点2.1 Stage 模型与 UIAbility 生命周期新手最容易忽略的就是 Ability 家族。OpenHarmony 的 Stage 模型里一个应用可以有一个或多个 UIAbilityUIAbility 负责和用户交互。记事本应用只需要一个入口就是工程里的 EntryAbility。UIAbility 的生命周期里最关键的是onCreate和onWindowStageCreate。onCreate适合做一些全局初始化比如初始化数据库onWindowStageCreate里加载首页相当于传统 Android 的setContentView。import UIAbility from ohos.app.ability.UIAbility; import window from ohos.window; import { NoteStore } from ../common/NoteStore; export default class EntryAbility extends UIAbility { onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { // 在这里初始化数据库后续页面可以直接使用 NoteStore.init(this.context).then(() { hilog.info(0x0000, NoteApp, NoteStore init done); }); } onWindowStageCreate(windowStage: window.WindowStage): void { windowStage.loadContent(pages/Index, (err) { if (err.code) { hilog.error(0x0000, NoteApp, loadContent failed: %{public}s, JSON.stringify(err)); return; } }); } }注意有些教程还在用 FA 模型里的mainAbility如果是新工程直接选 Stage 模型就行。从 API 9 开始 Stage 是主推方向照着它写以后迁移也省事。2.2 ArkTS 状态管理State 不是摆设在 ArkUI 里页面 UI 由状态驱动。最简单的入门写法是State修饰变量变量一变绑定了它的组件自动刷新。记事本首页核心状态是笔记数组State private noteList: Note[] [];这里有个细节State监听的是状态的变化不是“你做了什么”。如果直接修改数组内部的某个字段比如this.noteList[0].title 新标题UI 不一定刷新得过来。最稳妥的做法是让数组重新赋值// 推荐直接把新数组交给 State this.noteList result; // 或者使用展开运算符创建新数组 this.noteList [...this.noteList, newNote];很多新手写列表页时遇到“数据明明变了界面没反应”十有八九是这个原因。定义数据结构时也不要偷懒ArkTS 不允许到处用any建议在最开始就建好模型文件export interface Note { id: number; title: string; content: string; createTime: number; updateTime: number; }2.3 数据持久化方案怎么选OpenHarmony 提供的数据持久化方案有 Preferences、RelationalStore、分布式数据等。记事本我选 RelationalStore理由前面说过要做查询和排序。实际用下来它的 API 和 SQLite 类似写起来不陌生。方案适合场景缺点Preferences键值对、简单配置、小数据量不适合结构化查询数据量大后读写繁琐RelationalStore结构化业务数据、SQL 查询需要管理 ResultSetAPI 稍重分布式数据多设备同步需要分布式组网复杂度高如果你只是做一个 TODO 列表数据量极小Preferences 也不是不可以。但记事本一定会加搜索SQL 里一句LIKE就能搞定模糊查询比把全量数据拉到内存里用循环过滤舒服太多。3. 实操过程与核心环节实现3.1 工程创建与目录结构我用 IDE 新建了一个 Empty Ability 工程选择 Stage 模型语言选 ArkTS。工程创建后主要目录长这样entry/src/main/ets ├── entryability │ └── EntryAbility.ets ├── pages │ ├── Index.ets // 笔记列表页 │ └── EditNote.ets // 新建/编辑页 ├── model │ └── Note.ets // 数据模型 └── common └── NoteStore.ets // 数据库封装目录结构越早规划越好。很多入门项目写到最后所有代码都堆在 pages 里页面和数据库逻辑混在一起。我习惯把所有访问数据库的代码放在一个NoteStore类里页面只负责调用这样后面换存储方案或者接云端不用大改页面。3.2 实现笔记列表页列表页的核心是加载数据、展示数据、跳转编辑页、长按删除。import router from ohos.router; import { Note } from ../model/Note; import { NoteStore } from ../common/NoteStore; Entry Component struct Index { State noteList: Note[] []; State searchText: string ; State loading: boolean false; aboutToAppear(): void { this.reload(); } onPageShow(): void { // 从编辑页返回时重新拉一次数据 this.reload(); } reload(): void { if (this.searchText.trim().length 0) { NoteStore.search(this.searchText.trim()).then(rows { this.noteList rows; }); } else { NoteStore.queryAll().then(rows { this.noteList rows; }); } } deleteNote(id: number): void { NoteStore.deleteById(id).then(() { this.reload(); }); } build() { Column() { TextInput({ placeholder: 搜索笔记, text: this.searchText }) .onChange((value: string) { this.searchText value; this.reload(); }) .margin({ bottom: 8 }) List({ space: 8 }) { ForEach(this.noteList, (item: Note) { ListItem() { Column() { Text(item.title.length 0 ? item.title : 无标题) .fontSize(18) .fontWeight(FontWeight.Bold) Text(item.content.replace(/\n/g, )) .fontSize(14) .fontColor(#666666) Text(this.formatTime(item.updateTime)) .fontSize(12) .fontColor(#999999) } .width(100%) .padding(12) .backgroundColor(#FFFFFF) .borderRadius(8) .onClick(() { router.pushUrl({ url: pages/EditNote, params: { id: item.id } }); }) .gesture( LongPressGesture() .onAction(() { this.deleteNote(item.id); }) ) } }, (item: Note) item.id.toString()) } .layoutWeight(1) } .padding(16) .width(100%) .height(100%) } formatTime(time: number): string { const date new Date(time); const y date.getFullYear(); const m (date.getMonth() 1).toString().padStart(2, 0); const d date.getDate().toString().padStart(2, 0); const h date.getHours().toString().padStart(2, 0); const min date.getMinutes().toString().padStart(2, 0); return ${y}-${m}-${d} ${h}:${min}; } }ForEach 的第三个参数是 key 生成器一定要保证唯一。我直接用item.id.toString()千万不要用数组下标 index 当 key。用 index 的话删除中间一条数据后下一帧渲染可能因为 key 重复导致列表错乱。3.3 实现编辑页与自动保存编辑页要处理两种入口新建笔记时noteId -1编辑已有笔记时通过路由参数拿到 id。页面加载后根据 id 查数据库回填标题和正文。自动保存用的是防抖思路输入停止 800 毫秒后再落库避免每敲一个字符就写一次数据库。返回页面时再强制保存一次防止最后一下输入没触发定时器。import router from ohos.router; import promptAction from ohos.promptAction; import { NoteStore } from ../common/NoteStore; Entry Component struct EditNote { State title: string ; State content: string ; private noteId: number -1; private timer: number -1; aboutToAppear(): void { const params router.getParams() as Recordstring, number; if (params params.id) { this.noteId params.id; NoteStore.queryById(this.noteId).then((note) { if (note) { this.title note.title; this.content note.content; } }); } } scheduleSave(): void { if (this.timer 0) { clearTimeout(this.timer); } this.timer setTimeout(() { this.save(); }, 800); } save(): void { if (this.title.trim().length 0 this.content.trim().length 0) { // 空笔记不保存避免产生一堆垃圾数据 return; } if (this.noteId -1) { NoteStore.insert(this.title.trim(), this.content).then((newId) { this.noteId newId; }); } else { NoteStore.update(this.noteId, this.title.trim(), this.content); } } onPageHide(): void { this.save(); } build() { Column() { TextInput({ placeholder: 标题, text: this.title }) .onChange((value: string) { this.title value; this.scheduleSave(); }) .fontSize(20) .padding(12) TextArea({ placeholder: 正文, text: this.content }) .onChange((value: string) { this.content value; this.scheduleSave(); }) .layoutWeight(1) .padding(12) .fontSize(16) } .width(100%) .height(100%) } }这里有个容易忽略的坑新建第一条笔记时this.noteId还是 -1插入成功后一定要把返回的新 id 存到this.noteId。否则用户继续编辑程序不知道这是已有笔记又执行了一次 insert产生重复数据。3.4 数据持久化NoteStore 封装数据库这部分我封装成单例类核心方法包括初始化、查询所有、按 id 查询、插入、更新、删除、搜索。代码不算多但很值得仔细看。import relationalStore from ohos.data.relationalStore; import { Note } from ../model/Note; const STORE_CONFIG: relationalStore.StoreConfig { name: note.db, securityLevel: relationalStore.SecurityLevel.S1 }; const SQL_CREATE_TABLE CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, create_time INTEGER, update_time INTEGER ) ; export class NoteStore { private static store: relationalStore.RdbStore | undefined; static async init(context: Context): Promisevoid { if (NoteStore.store) { return; } NoteStore.store await relationalStore.getRdbStore(context, STORE_CONFIG); await NoteStore.store.executeSql(SQL_CREATE_TABLE); hilog.info(0x0000, NoteStore, init success); } private static getStore(): relationalStore.RdbStore { if (!NoteStore.store) { throw new Error(NoteStore not initialized); } return NoteStore.store; } static async queryAll(): PromiseNote[] { const store NoteStore.getStore(); const resultSet await store.querySql( SELECT * FROM notes ORDER BY update_time DESC ); return NoteStore.toNoteList(resultSet); } static async queryById(id: number): PromiseNote | undefined { const store NoteStore.getStore(); const resultSet await store.querySql( SELECT * FROM notes WHERE id ?, [id] ); const list NoteStore.toNoteList(resultSet); return list.length 0 ? list[0] : undefined; } static async insert(title: string, content: string): Promisenumber { const store NoteStore.getStore(); const now Date.now(); const values { title: title, content: content, create_time: now, update_time: now }; const rowId await store.insert(notes, values); return rowId; } static async update(id: number, title: string, content: string): Promisevoid { const store NoteStore.getStore(); const values { title: title, content: content, update_time: Date.now() }; await store.update(values, id ?, [id]); } static async deleteById(id: number): Promisevoid { const store NoteStore.getStore(); await store.delete(notes, id ?, [id]); } static async search(keyword: string): PromiseNote[] { const store NoteStore.getStore(); const likeKeyword %${keyword}%; const resultSet await store.querySql( SELECT * FROM notes WHERE title LIKE ? OR content LIKE ? ORDER BY update_time DESC, [likeKeyword, likeKeyword] ); return NoteStore.toNoteList(resultSet); } private static toNoteList(resultSet: relationalStore.ResultSet): Note[] { const list: Note[] []; while (resultSet.goToNextRow()) { const note: Note { id: resultSet.getLong(resultSet.getColumnIndex(id)), title: resultSet.getString(resultSet.getColumnIndex(title)), content: resultSet.getString(resultSet.getColumnIndex(content)), createTime: resultSet.getLong(resultSet.getColumnIndex(create_time)), updateTime: resultSet.getLong(resultSet.getColumnIndex(update_time)) }; list.push(note); } resultSet.close(); return list; } }有两个点特别提醒。第一ResultSet用完一定要 close。它持有数据库查询结果反复打开不关闭轻则内存警告重则后续查询报错。在toNoteList里即使中间有异常也应该保证 close 被调用实际项目建议用 try catch finally 包一层。第二insert方法返回的是rowId这个值就是自增主键 id可以直接拿来当新笔记 id。插入后把 rowId 存到编辑页的noteId字段后续更新就走 update 分支。4. 常见问题与排查技巧实录4.1 列表返回后不刷新很多人的第一个版本是从编辑页返回列表页数据没有变。原因是aboutToAppear只在页面首次创建时调用一次从编辑页返回时列表页的实例还在不会重新执行aboutToAppear。解决办法是在列表页里监听onPageShow每次页面显示都执行reload()。我一开始只写了aboutToAppear每次回列表都得手动下拉刷新才能看到新笔记后来加上onPageShow才彻底解决。4.2 RdbStore 初始化时机不对如果页面的queryAll在NoteStore.init之前执行会直接抛异常。尤其当你在aboutToAppear里初始化、又在同一个生命周期里查询时顺序容易翻车。最稳妥的初始化位置是 EntryAbility 的onCreate这个阶段应用上下文已经准备好NoteStore.init(this.context)在这个时机调用后续页面不管什么时候去查询store 都已经就绪。4.3 自动保存陷阱重复插入编辑页自动保存的常见 Bug 是用户输入几个字定时器触发 insert生成一条新笔记用户继续输入noteId还是 -1又插入一条。解决办法就是我前面说的insert 之后立即把返回的 id 赋值给this.noteId。另外还要处理空笔记问题。用户打开新建页一个字没打就返回这时候不应该创建记录。我在save()里加了一个判断标题和正文都为空时直接 return。4.4 ArkTS 严格模式别把 TS 当 JS 写ArkTS 比 TypeScript 严格得多。最常见的是no-any限制比如从router.getParams()拿参数时返回类型不明确直接用会报错。要手动做成类型断言const params router.getParams() as Recordstring, number;还有一点不要试图在 ArkTS 里使用Object类型的自由对象。定义接口、类或者用Recordstring, string来描述反而更安全。4.5 问题速查表现象大概率原因处理方式列表返回后不刷新缺少onPageShow在页面生命周期里重新 reload数据库报未初始化init 晚于查询在 EntryAbility 的 onCreate 里初始化输入后出现多条空笔记没有处理空内容 return保存前判空UI 不更新直接修改数组内部元素给State赋新数组搜索无结果关键字没做 trim搜索前keyword.trim()ResultSet 占用过多没有 close查询后立即 close5. 从记事本到完整应用的扩展路线第一版跑通之后后面能玩的空间其实很大。我自己的规划是先把富文本加上毕竟当前 TextArea 只能存纯文本换行符、简单 Markdown 语法都需要自己解析。然后是搜索优化。目前LIKE %keyword%在数据量小的时候没问题但当笔记数量到几千条可以考虑用词条拆分和索引优化。OpenHarmony 的关系型数据库支持 SQL能做的优化和传统 SQLite 差不多关键看你想存什么结构。还有一个我非常想试的方向是桌面卡片。OpenHarmony 的卡片机制可以把笔记摘要直接放到桌面上这是移动生态里很特别的能力。卡片需要单独的 FormExtensionAbility 和卡片配置文件工程结构会比纯页面复杂但底层数据依然是这张 notes 表复用现有 NoteStore 会很舒服。如果要做多端协同还可以关注分布式数据能力。把笔记存到分布式数据库后手机、平板、开发板之间可以共享数据。不过这个方向涉及组网和设备认证建议等基础功能稳定之后再投入否则很容易被环境问题打断。我在实际开发中最深的一个体会是OpenHarmony 应用开发看着全是新概念但底层逻辑并不难。状态驱动 UI、SQL 存数据、Ability 管生命周期这些和主流移动开发思路相通。记事本 APP 的价值在于用最小成本把这些概念全部揉进一个项目里只要把这篇里的代码跑通你对 OpenHarmony 应用开发的骨架就算立起来了。接下来再写登录、列表详情、设置页都会顺手很多。

相关新闻

Mininet网络实验完全指南:从拓扑构建到流表观测的SDN实践

Mininet网络实验完全指南:从拓扑构建到流表观测的SDN实践

简介:这是一份面向网络工程、软件定义网络(SDN)初学者的 Mininet 实验报告,完整记录了从可视化建拓扑到 Python 脚本构建拓扑的全过程。报告覆盖 Miniedit 可视化工具创建、命令行创建、交互式界面创建主机与交换机、节点间 ping …

2026/10/12 9:39:18 阅读更多 →
Nakagami-m信道仿真从理论到落地:原理、参数估计与误码率验证

Nakagami-m信道仿真从理论到落地:原理、参数估计与误码率验证

1. 从一次仿真翻车说起:为什么我要死磕Nakagami-m信道几年前我帮一个做无线通信的团队调一套链路级仿真平台,当时他们用的是瑞利衰落模型,结果实测数据和仿真结果怎么都对不上——误码率曲线在低信噪比区间差了将近一个数量级。排查了整整两周…

2026/10/12 9:39:18 阅读更多 →
工控AI落地实战:边缘推理、预测性维护与数字孪生的确定性工程路径

工控AI落地实战:边缘推理、预测性维护与数字孪生的确定性工程路径

1. 这份报告不是“预测”,而是工控现场工程师的五年作战地图“工控AI发展方向深度研究报告(2026-2030)”——看到这个标题,很多同行第一反应是:又一份堆砌术语、罗列概念的PPT式行业白皮书?我干了12年自动化…

2026/10/12 9:39:18 阅读更多 →

最新新闻

Git+Repo+Gerrit代码评审服务器搭建实战:多仓库协作与权限管理

Git+Repo+Gerrit代码评审服务器搭建实战:多仓库协作与权限管理

简介:本资源是一份面向运维工程师、后端开发及代码管理初学者的代码评审服务器搭建文档,围绕 Git、Repo 与 Gerrit 三大工具,讲解如何从零构建一套功能完整的代码服务器,解决团队代码托管、多仓库统一管理与代码评审流程落地的问题…

2026/10/12 10:21:08 阅读更多 →
零侵入监控:node_exporter、mtail 与 Prometheus、Grafana 组合实战

零侵入监控:node_exporter、mtail 与 Prometheus、Grafana 组合实战

1. 先统一认知:node_exporter、mtail、Prometheus、Grafana 各管哪一段我见过很多团队做监控,第一反应是“先装一套 Prometheus Grafana”,装上之后发现只有 CPU、内存、磁盘这些机器指标,业务到底跑得好不好完全看不见。后来有人…

2026/10/12 10:21:08 阅读更多 →
Wi-FiTestSuite Control API v10.12.0规范解析:面向Wi-Fi 6E自动化测试的会话化控制

Wi-FiTestSuite Control API v10.12.0规范解析:面向Wi-Fi 6E自动化测试的会话化控制

简介:本资源是Wi-Fi联盟官方发布的《Wi-Fi Test Suite Control API Specification Version 10.12.0》PDF规范文档,面向Wi-Fi设备认证工程师、无线协议开发者及测试工具集成人员,用于指导Wi-Fi认证测试套件的开发、调试与合规性验证。文档系统…

2026/10/12 10:21:08 阅读更多 →
安卓17 Beta版修复待机耗电:原理、实测与开发者适配指南

安卓17 Beta版修复待机耗电:原理、实测与开发者适配指南

安卓17最新Beta版的推送消息出来后,论坛上不少刷过测试版的人开始讨论同一个话题:这次官方终于对“待机耗电”动手了。说实话,“Beta版更新修复耗电”并不是新闻,每代大版本更新都会经历类似的阶段,但这次修复的幅度和…

2026/10/12 10:21:08 阅读更多 →
Spring事务失效陷阱:Closing non transactional SqlSession排查与修复

Spring事务失效陷阱:Closing non transactional SqlSession排查与修复

1. 从一个诡异的线上故障说起"Closing non transactional SqlSession"——这行日志第一次出现在我视野里的时候,是一个再普通不过的周三下午。某个业务系统在批量处理数据时,偶发地出现部分数据写入成功、部分数据回滚的诡异现象。日志里没有明…

2026/10/12 10:21:08 阅读更多 →
Nginx基于IP的虚拟主机配置:多IP绑定、listen指令与踩坑实战

Nginx基于IP的虚拟主机配置:多IP绑定、listen指令与踩坑实战

做运维这些年,配置虚拟主机绝大多数时候都在跟域名打交道,但有一类需求绕不开基于IP的场景:客户拿着独立IP的SSL证书要求绑定;内网有多套环境要用不同IP做物理隔离;服务器上买了多个公网IP想分别承载不同业务。今天把基…

2026/10/12 10:20:06 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →