【uni-app UTS 插件】三端电子书阅读器:Android / iOS / 鸿蒙 Next 一套 API + 百搭阅读壳

插件 ID:ebook-reader
当前版本:1.9.2
支持:Vue2 / Vue3 · app-vue · app-nvue · app-uvue · Android · iOS · 鸿蒙 Next · H5 轻量降级
错误码段:8040xxx
配套:ebook-select-files(选书)· ebook-reader-handwrite(手写)· ebook-reader-ai(AI)


一、为什么跨端电子书这么难做?

在 uni-app 里做「像样的阅读器」,很多人会先想到 WebView + epub.js。上线一两个版本后,常见坑会成批出现:

渲染与性能

  • 大 txt / epub 整本塞进 WebView,低端机内存暴涨甚至闪退
  • 分页、字号、行距一改就要整页重排,滚动位置容易错乱
  • 仿真翻页、听书断句、选区划线,Web 方案体验参差不齐

三端原生差异

  • Android TTS 要音频焦点 + 媒体通知,后台才稳
  • iOS 后台朗读要开音频会话,字体要用 CTFont
  • 鸿蒙权限写在 module.json5,TTS 走 CoreSpeechKit,和 Android/iOS API 完全两套

产品边界

  • 小说 App 要书架进度同步;教育 App 要笔记导出;B 端要水印和到期策略
  • 手写 OCR、AI 划词、PDF 渲染体积大,不该全塞进一个主包

自己从零写三端原生阅读内核,周期以月计。有没有一套 UTS 插件,既能开箱嵌入页面,又能用纯 API 自绘 UI?


二、ebook-reader:内核 + 百搭阅读壳

ebook-reader 是一个 UTS 原生插件,用一套 JavaScript API 覆盖 Android、iOS、鸿蒙 Next 的本地电子书阅读场景,并附带可直接嵌入的 Vue 组件壳。

典型用途:

  • 小说 / 网文阅读页
  • 教材 / 教辅 / 学习 App 内阅读
  • 企业内部分发文档(水印 + 访问策略)
  • 需要自研书架 UI、只接阅读内核的项目

两种接入姿势

姿势适合怎么做
百搭阅读壳快速上线阅读页<ebook-reader path="..." />
内核 API完全自定义 UIcreateReader → openBook → getPageContent

主包当前定位(1.9):

  • ✅ txt / md / epub 文本层(含封面、简介、图片抽出)
  • ✅ 分页 · 主题 · 字号 · 书签笔记 · TTS · 选区 · 手势 · 水印
  • ✅ 阅读壳内 Canvas 贝塞尔仿真卷曲(pageMode=simulation)
  • ❌ 完整 CSS 图文混排引擎、字体二进制打进主包、PDF / 古籍竖排 / 加密(增值或后续大版本)

三、5 分钟开箱接入(阅读壳)

1. 安装

将插件放入项目 uni_modules/ebook-reader,制作自定义调试基座后运行(改 utssdk/ 必须重做基座)。选书推荐同时装 ebook-select-files;鸿蒙再装 ebook-select-files-harmony。

2. 最小页面

easycom 会自动注册组件。宿主页面:

<template>
  <ebook-reader
    :path="bookPath"
    :title="bookTitle"
    :show-back="true"
    watermark="仅供内部阅读"
    @progress="onProgress"
    @back="onBack"
    @error="onError"
  />
</template>

<script>
export default {
  data() {
    return {
      bookPath: '', // pickBookFile({ copyToCache: true }) 后的稳定 path
      bookTitle: '我的书'
    }
  },
  methods: {
    onProgress(p) {
      // 同步自有书架:p.page / p.percent / p.position
      console.log(p.page, p.percent)
    },
    onBack() {
      uni.navigateBack()
    },
    onError(err) {
      console.error(err.code, err.message)
    }
  }
}
</script>

3. 书源三选一

Prop说明
path本地沙盒路径(选书务必 copyToCache: true)
url网络下书后打开
content内存正文;H5 / 任意端快速试读

命令式也可以:

this.$refs.reader.open({ path: '/path/to/a.epub', bookId: 'b1' })
this.$refs.reader.open({ content: '第一章\n\n正文…', format: 'txt' })
this.$refs.reader.pickAndOpen()

4. 壳内交互(用户无需再写)

  • 点中央:显隐顶/底栏
  • 点左右约 28% 或滑动:翻页
  • simulation:Canvas 贝塞尔卷曲,跟手拖动;可开纸张音效
  • 双指捏合调字号;上下滑调亮度
  • 长按:复制 / 划线 / 笔记 / 朗读
  • 底栏:目录 · 主题(冷纸/夜墨/青苔/旧笺)· Aa · 书签 · 听书

插件自带的 index.vue 就是这个壳的演示页(含试读样例),可直接对照。


四、完全自定义:内核 API

不需要内置 UI 时,只接内核:

import {
  createReader,
  openBook,
  getPageContent,
  nextPage,
  onReaderEvent,
  getReaderCapabilities,
  pickBookFile
} from '@/uni_modules/ebook-reader'

const caps = getReaderCapabilities()
console.log(caps.platform, caps.supportsEpub, caps.supportsTts)

const { readerId } = await createReader({
  theme: 'day',
  viewportWidth: 360,
  viewportHeight: 640,
  statsIntervalMs: 30000
})

onReaderEvent(readerId, 'progress', (p) => {
  console.log(p.page, p.pageCount, p.percent)
})

onReaderEvent(readerId, 'behavior', (e) => {
  // 只抛不传:由业务层自己上报
  console.log(e)
})

const pick = await pickBookFile({
  count: 1,
  extensions: ['txt', 'md', 'epub'],
  copyToCache: true
})
if (!pick.ok) {
  // 用户取消:8040001
  return
}

await openBook(readerId, {
  path: pick.files[0].path,
  bookId: 'my_book',
  restoreProgress: true
})

const page = await getPageContent(readerId)
console.log(page.text, page.images)
await nextPage(readerId)

重要:UTS 桥接不回传带方法的 class,所有 API 以 readerId 为第一参数。不要指望拿到一个「Reader 实例对象」再调方法。

打开方式汇总

// 本地路径
await openBook(readerId, { path: '/sandbox/a.epub', bookId: 'b1' })

// 网络 URL(内部 downloadBook)
await openBook(readerId, { url: 'https://cdn.example.com/a.epub' })

// 内存正文(含 H5)
await openBook(readerId, { path: 'content:第一章\n\n正文', format: 'txt' })

// Base64 字节流
await openBook(readerId, { bytesBase64: '...', format: 'txt' })

五、三端分别做了什么?

Android

  • 文本分页 + epub 文本层 / 图片抽出
  • 原生页:TextView overlay;uni-app x 可 embed 到 ViewGroup
  • TTS:音频焦点 + MediaSession 媒体通知(后台听书)
  • 环境光、口袋模式(距离传感器)
  • 翻页音效:ToneGenerator
  • 自定义字体:Typeface

iOS

  • 同构内核 API;原生页 UITextView
  • embed:按 accessibilityIdentifier 找容器
  • TTS:后台音频会话
  • 翻页音效:SystemSound
  • 自定义字体:CTFont
  • setTheme('system') 跟随深色模式

鸿蒙 Next

  • 权限等声明走 module.json5(只写 config.json 不会进包)
  • TTS:@kit.CoreSpeechKit
  • 环境光、口袋模式可用
  • 原生页目前以系统对话框 overlay 为主
  • 翻页音效等能力以事件抛给宿主 UI

H5

  • 轻量降级:content / content: 试读
  • 无本地选书、无完整 epub、无原生 TTS/传感器
  • 阅读壳仍可跑通交互原型

六、几个实用特性详解

1. epub 文本层(不是完整 CSS 引擎)

当前主包解析路径:

META-INF/container.xml → OPF manifest/spine → 章节 HTML 抽纯文本

  • 封面 / 简介:BookMeta.coverPath / description
  • 图片:正文占位 [图 src="..." alt="..."],PageContent.images[].localPath 可给宿主展示
  • 目录:优先 NCX / nav,否则文内首行

适合「小说正文阅读」;复杂图文杂志排版需等完整 CSS 引擎大版本,或自研宿主渲染。

2. 仿真翻页:两套实现别混用

场景实现
阅读壳 pageMode=simulationCanvas 贝塞尔卷曲(er-page-curl),跟手拖动
原生页 showNativeReaderAndroid/iOS 轻量位移动画(非物理卷曲)

壳内仿真已可用于 App-Vue 验证;原生层物理卷曲仍属后续规划。

3. TTS 听书

await ttsPlay(readerId, { fromSelection: false })
await ttsPause(readerId)
await ttsNext(readerId) // 翻页并继续读
await ttsPrev(readerId)
await setPauseMarksVisible(readerId, null) // null = 跟随 TTS 自动开断句标记

Android 可出媒体通知;iOS 开后台会话;鸿蒙走系统 Speech Kit。

4. B 端:水印 + 访问策略

await setWatermark(readerId, { text: '内部资料', opacity: 0.12, enabled: true })
await setBookAccessPolicy({
  bookId: 'lease_001',
  expireAt: Date.now() + 7 * 86400000,
  allowOpen: true
})
// 到期 openBook → 8040106;禁止打开 → 8040107

行为埋点事件(bookOpen / pageTurn / dwell / selection 等)只抛不传,上报由业务层自己做。

5. 摘抄导出

const md = await exportNotes(readerId, 'md')
await copyExportToClipboard(readerId, 'json')

适合学习类 App 一键导出划线 + 笔记 + 页码。


七、增值模块怎么拆?

主包刻意做「薄」:同名 API 在主包是 stub,未安装时统一 8040004。

// 手写:请从增值包 import
import { enableHandwrite, ocrHandwrite } from '@/uni_modules/ebook-reader-handwrite'

// AI:不内置大模型,只调你的 endpoint
import { configureAi, aiLookup } from '@/uni_modules/ebook-reader-ai'

await configureAi({
  apiKey: 'sk-xxx',
  endpoint: 'https://your.api/v1'
})
const r = await aiLookup('ephemeral', 'dict')
模块能力摘要
ebook-reader-handwrite笔迹叠加、持久化、端侧 OCR、批注朗读
ebook-reader-ai划词释义、章节摘要、抽词、生词卡(业务 HTTP)

规划中:ebook-reader-pdf、ebook-reader-ancient、ebook-reader-secure。


八、能力矩阵速览(1.9)

能力AndroidiOS鸿蒙H5
阅读壳✅✅✅✅(content)
壳内贝塞尔卷曲✅✅✅✅
txt / md✅✅✅✅
epub 文本层✅✅✅❌
pickBookFile✅✅✅❌
TTS✅✅✅❌
后台 TTS✅ 通知✅❌❌
原生页 overlay✅✅✅ 对话框❌
环境光 / 口袋模式✅ / ✅❌ / ❌✅ / ✅❌
水印 / 访问策略✅✅✅✅

更细的 Props / 事件 / API 表见插件 readme.md。


九、错误码与踩坑

常见错误码

code含义
8040001用户取消选书
8040002参数非法
8040003平台不支持
8040004增值模块未安装
8040100文件不存在
8040105书籍过大(约 20MB 上限)
8040106 / 8040107过期 / 禁止打开
8040200阅读器未创建或原生页容器失败
8040201未打开书籍
8040300字体失败
8040400TTS 不可用

必记踩坑

  1. 改原生后重做自定义基座
  2. 目录名 = package.json 的 id = ebook-reader
  3. 禁止导出名为 init 的函数(iOS/Swift 保留字,云打包会挂)
  4. 选书务必 copyToCache: true,再用稳定 path 打开
  5. 鸿蒙权限必须进 module.json5 + $string:reason
  6. UTS 返回值禁止 Promise<{...}>,要用命名类型(如 FontInstallResult)

十、隐私与市场声明(可直接改写)

  1. 权限:主包无强制危险权限;选书随系统文件选择;按需网络下书、TTS 后台音频/通知、环境光/距离传感器
  2. 数据:默认仅本地存储进度 / 书签 / 笔记;行为事件只抛给宿主;AI 由业务自配服务端
  3. 广告:无

十一、版本演进(摘要)

版本亮点
1.0txt/md 分页、书签笔记、三端 + Web
1.1epub 文本层、前台 TTS
1.2–1.3下书、简繁、音效、水印、口袋模式、导出
1.4–1.6选区手势、原生页、后台 TTS 通知
1.7–1.8epub 图片、鸿蒙 TTS、选书、封面简介
1.9.0百搭阅读壳开箱即用
1.9.1手写 / AI stub + 独立增值包
1.9.2FontInstallResult 修复;壳内 Canvas 贝塞尔卷曲

完整条目见插件 changelog.md。


十二、总结

如果你在做 uni-app 的小说、教育或企业阅读场景,又不想维护三套原生阅读器:

  1. 要快:挂 <ebook-reader>,进度事件回写书架
  2. 要自由:只用 createReader / openBook / getPageContent 自绘
  3. 要增值:按需装 handwrite / AI,主包保持体积可控

一套 readerId API,Android / iOS / 鸿蒙共用;错误码统一 8040xxx,便于日志和客服排查。

欢迎在评论区交流接入问题;插件市场与更新日志以插件包内 readme.md / changelog.md 为准。

Logo

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

更多推荐