# 小说阅读器

小说阅读器是微信官方为小说类目小程序提供的阅读器插件。

为保障平台小说小程序规范运营和用户的阅读及使用体验，符合小说类判定的小程序，需申请【文娱-小说】类目，并统一使用官方阅读组件进行内容投放。

代码示例：[代码片段示例](https://developers.weixin.qq.com/s/Tk0KHtmz72P2)

效果示例：

![image.png](https://res.wx.qq.com/op_res/efAgcZJWHugUaOG95znar6v87kLg_N889C0i92yR9B16FS9F5qVUSXptyiF82c3fEefH67_1blPueyJSaybKGw)

![image.png](https://res.wx.qq.com/op_res/efAgcZJWHugUaOG95znar8tRsJe0pXO8Hb1OMLUqHefKgfl7EZayRW-i9RI8rheEjkHROwkyVSd0ZkPxky_QuQ)

![image.png](https://res.wx.qq.com/op_res/efAgcZJWHugUaOG95znar12hY52cVT4G2E6V56qLm8CdlJRwGB9BXvYmU7RyXq7z-lICY-0jlp-b9s6YhThc3g)

![image.png](https://res.wx.qq.com/op_res/efAgcZJWHugUaOG95znarxaMycaakrD4F-kSDjXSOW_aSsGiXelkn-taQeqRIrl9DuGaTdEEcyUx1fVVw5ua9A)

## 一、接入流程

### 1.1 添加插件

小说类目小程序默认开通插件权限。

首先需要在小程序的 `app.json` 文件中添加插件的引入配置，示例代码如下：

```json
{
  "plugins": {
    "novel-plugin": {
      "version": "latest",
      "provider": "wx293c4b6097a8a4d0"
    }
  }
}
```

> `provider` 字段为插件提供方的 AppID，设置为 `wx293c4b6097a8a4d0` 即可。请确保将 `version` 字段设置为 `latest`，以使用最新版本的插件。

### 1.2 插件初始化

插件页与小程序之间是通过**事件**来进行通信的，这就意味着小程序需要编写相应的订阅事件和发布事件逻辑。

在插件页初始化完毕后，会发送一个事件告知给小程序，插件页初始化完毕，小程序端需要订阅这个事件，示例代码如下：

```js
// app.js
// 引入阅读器插件
// 详见/miniprogram/dev/reference/api/requirePlugin.md
const novelPlugin = requirePlugin('novel-plugin')

App({
  onLaunch() {
    // 监听进入插件页事件
    novelPlugin.onPageLoad(onNovelPluginLoad)
  },
})

function onNovelPluginLoad(data) {
  // data.id - 阅读器实例 ID，每个插件页对应一个阅读器实例
  const novelManager = novelPlugin.getNovelManager(data.id)

  // getId() 返回当前阅读器实例 ID
  console.log('id:', novelManager.getId())

  // 设置目录状态
  novelManager.setContents({
    contents: [
      {
        index: 0, // 第一章
        status: 0, // 免费
      },
      {
        index: 1, // 第二章
        status: 2, // 未解锁
      },
      {
        index: 2, // 第三章
        status: 1, // 已解锁
      },
      // ...
    ],
  })

  // 监听用户行为事件
  novelManager.onUserTriggerEvent(res => {
    console.log('onUserTriggerEvent', res.event_id, res)
  })
}
```

> 1. 该逻辑建议放在 app.js 中，确保用户直接进入阅读器插件页时也能正确处理。
> 2. onPageLoad 需要传入一个回调函数来接受从插件传入的值。
> 3. onPageLoad 在进入插件页后才会触发。

### 1.3 跳转插件页

插件页的路径为 `plugin-private://wx293c4b6097a8a4d0/pages/novel/index?bookId=xxx`，该路径可以当成普通页面路径进行使用，通过 `navigateTo` 或 `redirectTo` 等方法进行跳转。

```js
wx.redirectTo({
  url: 'plugin-private://wx293c4b6097a8a4d0/pages/novel/index?bookId=xxx'
})
```

支持参数：

| 参数 | 必填 | 描述 |
|--|--|--|
| bookId | 是 | 书籍 ID |
| customServerParams | 否 | 透传到服务器的参数，因为是在 path 上，所以如果含有特殊字符，需自行 encode，防止解析出错 |
| chapterIndex | 否 | 跳转章节下标（从 0 开始） |
| paragraphIndex | 否 | 跳转段落下标，默认 0（从 0 开始） |
| fontSize | 否 | 指定默认字体大小（0 - 9 从小到大） |
| turnPageWay | 否 | 指定默认翻页方式：TURN_PAGE_WAY_SWIPE - 覆盖翻页；TURN_PAGE_WAY_MOVE - 平移翻页；TURN_PAGE_WAY_SCROLL - 上下翻页 |
| backgroundConfigIndex | 否 | 指定默认背景色序号，默认为 1（可传入值 1 - 5） |
| isNightMode | 否 | 指定默认是否夜间模式，传入 1 则表示默认为夜间模式 |
| blockUnpaidScroll | 否 | 传入值为 1 时，滑动到未解锁章节会阻塞住，用户无法往下滑动；传入值为 0 时则不做阻塞；默认值为 1 |
| disableAutoShowChargeDialog | 否 | 传入值为 0 时，进入未解锁章节时会自动拉起付费弹窗；传入值为 1 时则关闭此特性；默认值为 0 |
| showListenButton | 否 | 是否显示听书按钮，传入值为 1 时则显示听书按钮；默认为 0，即不显示 |
| listenForFree | 否 | 传入值为 1 时表示全书可免费听；传入值为 0 时表示在听书时遇到未解锁内容时会自动停止；默认值为 0 |
| recommendMode | 否 | 传入值为 1 时显示读后推荐列表；传入值为 2 时显示读后推荐沉浸模式；默认值为 1 |

> 1. bookId 是书籍 ID，创建作品后会返回，详情可参考[小说作品管理接口文档](novel-api)
> 2. chapterIndex 和 paragraphIndex 用于强制跳转到指定章节和段落；用户的阅读进度由阅读器自行保存，无需接入方维护。
> 3. fontSize 参数用于修改默认字体大小，若用户已经设置过字体大小，则该参数无效。turnPageWay 参数同理。
> 4. 如果当前书籍的音频未生成，就算传入 showListenButton 也不会显示听书按钮。

### 1.4 章节解锁组件

在未解锁章节末尾默认会有一个按钮，点击按钮后会拉起半屏，半屏中即是需要用户传入自定义组件的[抽象节点](/miniprogram/dev/framework/custom-component/generics.md)。

> iOS 中默认是显示一段提示而非按钮，可通过 setChargeWay 接口让按钮显示

![image.png](https://res.wx.qq.com/op_res/-UFwsdSIh049wLQR211RYbzTZW8QCAE74eObfP7DWxehLnFIhlNLLu6038_mPkhy-PfzEMlbbU6L31pZhCsISg)

首先需要编写一个自定义组件 charge-dialog，该组件和普通自定义组件编写规则一致：

```html
<!-- charge-dialog.wxml，以下代码仅供参考 -->
<button bind:tap="unlock">解锁第 {{ chapterIndex + 1 }} 章</button>
```

```js
// charge-dialog.js，以下代码仅供参考
const novelPlugin = requirePlugin('novel-plugin')

Component({
  properties: {
    novelManagerId: {
      type: Number,
      value: -1,
    },
    bookId: {
      type: String,
      value: '',
    },
    chapterIndex: {
      type: Number,
      value: -1,
    },
    chapterId: {
      type: String,
      value: '',
    },
    originalId: {
      type: String,
      value: '',
    },
  },

  methods: {
    unlock() {
      // 取出对应的阅读器实例
      const novelManager = novelPlugin.getNovelManager(this.properties.novelManagerId)

      // do something

      // 告诉阅读器这一章已解锁
      novelManager.paymentCompleted()
    },
  },
})
```

然后在 app.json 中将此组件作为抽象节点添加到插件设置中：

```json
{
  "plugins": {
    "novel-plugin": {
      "version": "latest",
      "provider": "wx293c4b6097a8a4d0",
      "genericsImplementation": {
        "novel": {
          "charge-dialog": "path/to/component"
        }
      }
    }
  }
}
```

阅读器插件会传入以下属性给该组件：

| 参数 | 类型 | 描述 |
|--|--|--|
| novelManagerId | Number | 阅读器实例 ID |
| bookId | String | 书籍 ID |
| chapterIndex | Number | 章节下标 |
| chapterId | String | 章节 ID |
| originalId | String | 该属性是否传入取决于接入方是否上传该属性 |

> 1. 解锁流程结束后，应调用 paymentCompleted 接口通知阅读器解锁完成，以确保页面能够正确更新。
> 2. 抽象节点一般是为阅读器插件准备，小程序本身不会直接引用它们。在某些构建环境下，由于组件未被使用，可能会被编译工具进行优化，导致阅读器无法找到插件。如果遇到这种情况，请检查编译产物，确保组件代码存在。如果组件代码不存在，可以在某个页面中引用该组件（即使不使用），以确保组件不会被优化掉。
> 3. 可以使用 setChargeWay 接口来自定义收费组件的文案和行为，以满足特定的业务需求。
> 4. 可以使用 openChargeDialog、closeChargeDialog 接口来主动打开/关闭章节解锁组件。
> 5. 解锁组件最大高度是屏幕高度的 75%，若有较长内容时，开发者应使用 scroll-view 组件。

### 1.5 章节解锁链路

章节解锁状态由开发者后台维护，在需要确认章节状态时（比如在调用 paymentCompleted 接口后），阅读器后台会询问开发者后台此章节的解锁状态，因此整个解锁流程还需要开发者后台接入。

![image.png](https://res.wx.qq.com/op_res/BTvgaPB0e73jbxhoJYyqnFeVszmI8-aAI2kSpQIsNPLUhIbrgBB9JcoNSw45Oc5kx2L2wysOYLnS-ioLi_zrcw)

![image.png](https://res.wx.qq.com/op_res/BTvgaPB0e73jbxhoJYyqnFWm05KahQKvXZ7mDKQh66Lu9TRuF-t2rVZTQslyolQUtrAuqj01p84ul2A96CPzeQ)

#### 1.5.1 消息推送

微信和开发者之间是通过[消息推送](/miniprogram/dev/framework/server-ability/message-push.md)来传递章节解锁信息，故需要开发者开通消息推送。

1. 开发者服务器接收消息推送 - 支持
2. 云函数接收消息推送 - 支持
3. 微信云托管服务接收消息推送 - 支持
4. 第三方服务器接收消息推送 （小说权限集授权给第三方）- 支持

> 注意：这里消息推送超时时间为 4s（常规消息推送是 5s）。

#### 1.5.2 消息格式

接收的数据格式

| 参数 | 类型 | 说明 |
|:--|:--:|:--|
| ToUserName | String | 小程序原始 ID |
| FromUserName | String | 用户 openid |
| CreateTime | Number | 消息发送时间 |
| MsgType | String | 固定值：event |
| Event | String | 固定值：wxa_novel_chapter_permission |
| BookId | String | 作品 ID |
| ChapterIndex | Number | **章节编号，数值从 0 开始，所以第一章的 ChapterIndex 为 0** |
| ChapterId | String | **章节 ID** |
| CustomServerParams | String | 透传自定义内容 |
| Source | Number | 0 - 插件预加载对应章节，不是用户实际读；1 - 用户读到对应章节；**开发者可针对Source=1做自动解锁逻辑** |
| ChapterOriginalId | String | 章节主键 original_id，如果开发者上传章节时没传 original_id，则 ChapterOriginalId 的值为空 |
| BookOriginalId | String | 书籍主键 original_id，如果开发者创建作品时没传 original_id，则 BookOriginalId 的值为空 |

![image.png](https://res.wx.qq.com/op_res/dav9egGvMDfDxUmRysKxlzYPjnN4J7rPnYeRAsDsVSwlgevVeMhF0WMEYnrNIOdKt9O2P7kPrKz0yuEsy3T_sQ)

```json
以JSON格式举例，XML格式是类似的
明文模式
{
	"ToUserName": "gh_xxx",
	"FromUserName": "orI6161IRrMgF3t3KfVFPOKf4nsk",
	"CreateTime": 1706085351,
	"MsgType": "event",
	"Event": "wxa_novel_chapter_permission",
	"BookId": "A1L2CaTvE3686DAK676Cqf372r",
	"ChapterIndex": 0,
	"ChapterId": "xxxxxx",
   "CustomServerParams":"test",
   "Source": 1
}

兼容模式
{
	"ToUserName": "gh_xxxxx",
	"FromUserName": "orI6161IRrMgF3t3KfVFPOKf4nsk",
	"CreateTime": 1706083040,
	"MsgType": "event",
	"Event": "wxa_novel_chapter_permission",
	"Encrypt": "V6GK8lE/L1SdPFYxzwXKg00hOSguFRaPwyKMQDmztT9aG/hN/4YBda9kqQfGgJjkOIhwYnTnWIuTwyZ+Ux+rqa/4r6wverGNny6EWp6UI0oBh6V/5ujqgk9CptjTPxuCFn/1ZthVeOBR/vRHXPAMgE1m1QTIb9HLpRnV/IcuufdHLz1zh0Gzeq/HiqNjYVcxbgxtxfDwAcBsVQZF1DUWr3rg9h6Y3nawW5UAra9kIsAgea0iXo/jPzwUEcfnUPMERBD8nBd7vDDhYtSrOD4Ywx9JuOqvMjULM3GzYqBffhVqUFSmQdkOBo0Qr6IK1CAjVw20vC0b5bmMJj+3JYIXVA==",
	"BookId": "A1L2CaTvE3686DAK676Cqf372r",
	"ChapterIndex": 2,
	"ChapterId": "xxxxxx",
	"CustomServerParams":"test"
}

加密模式
{
	"ToUserName": "gh_xxx",
	"Encrypt": "5BJZyHGl1XiZx05r2bO4YZJYoE6jGaDQAGg34pCID/C3zJv68i5ggOqbNdUDfFwL5iCSqv2cBnX2sX5+SgKp6Nr8PwGxajKdTEFUXDDYzf//hiEIZm1CdqwfVZAeKy7tDNz0q//ST8yO6ucRC1X/p4PUMHUayXU9DZQolfIsbiPIQ20gsq290T1n5HmnCIwuZfexqE9sRgVWTp3LnfmdgqPDVJe13Yv31FbYPC/1BXZfcRPEt0rWAvW1XtWSHeEbK2vKKQ/e1uzjChynTKHYUNwNEbD9JAkrNtdY5eHuIgaOxxvYGp/Rldnzg1kyTHSFs9y7BYGQw0WlpTabLBRzsg=="
}

```

回包格式

| 参数 | 类型 | 必填 | 说明 |
|:--|:--:|:--:|:--|
| ErrCode | Number | 是 | 发送状态：0 - 成功；其他 - 失败 |
| ErrMsg | String | 否 | 错误原因，用于调试。在 ErrCode 非 0 的情况下可以返回 |
| ChapterPerms | `Array<ChapterPermission>` | 是 | 返回章节的具体解锁情况，鼓励开发者返回的 ChapterPerms 包含更多的章节解锁信息（包括已解锁和未解锁章节） |

ChapterPermission 参数结构如下：

| 参数 | 类型 | 说明 |
|:--|:--:|:--|
| StartChapterIndex | Number | 起始章节 |
| EndChapterIndex | Number | 结束章节 |
| Perm | Number | 0 - 免费；1 - 已解锁；2 - 未解锁 |

```json
注意：消息推送格式选JSON时，这里的回报格式也需要是json
注意：消息推送格式选XML时，这里的回报格式目前支持xml和json(优先推荐xml格式，这里支持json只是为了兼容已接入的小程序)
注意：鼓励开发者返回的ChapterPerms包含更多的章节解锁信息（包括已解锁和未解锁章节）

{
	"ToUserName": "gh_xxx",
	"FromUserName": "orI6161IRrMgF3t3KfVFPOKf4nsk",
	"CreateTime": 1706085351,
	"MsgType": "event",
	"Event": "wxa_novel_chapter_permission",
	"BookId": "A1L2CaTvE3686DAK676Cqf372r",
	"ChapterIndex": 0,
	"ChapterId": "xxxxxx",
   "CustomServerParams":"test",
   "Source": 1
}

{
	"ErrCode": 0,
	"ErrMsg": "",
	"ChapterPerms": [{
		"StartChapterIndex": 0,
		"EndChapterIndex": 2,
		"Perm": 1
	}, {
		"StartChapterIndex": 3,
		"EndChapterIndex": 5,
		"Perm": 2
	}]
}
```

## 二、接口列表

### 2.1 基础

#### novelPlugin

阅读器插件，通过 requirePlugin 引入。

```js
const novelPlugin = requirePlugin('novel-plugin')
```

#### novelPlugin.onPageLoad(Function callback)

当用户进入阅读器插件页时便会触发此事件。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| id | Number | 当前插件页对应的阅读器实例 ID |

```js
const novelPlugin = requirePlugin('novel-plugin')

novelPlugin.onPageLoad(params => {
  // do something
})
```

#### NovelManager novelPlugin.getNovelManager(Number id)

获取阅读器实例，通过此实例可对该插件页进行操作管理。

传入的参数 id 为当前插件页对应的阅读器实例 ID，可在 onPageLoad 中获取。

```js
const novelPlugin = requirePlugin('novel-plugin')

const novelManager = novelPlugin.getNovelManager(id)
```

#### novelPlugin.getCurrentNovelManager()

获取当前页的阅读器实例。如果当前页非正常可用的插件页，则不会返回。

> 通过此接口亦可判断当前页是否阅读器插件页。

```js
const novelPlugin = requirePlugin('novel-plugin')

const novelManager = novelPlugin.getCurrentNovelManager()
```

#### novelPlugin.setLoggerConfig(Object params)

设置插件日志显示级别，默认只显示 warn 和 error。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| info | Boolean | 否 | 是否输出 info 日志，默认为 false |
| debug | Boolean | 否 | 是否输出 debug 日志，默认为 false |
| log | Boolean | 否 | 是否输出 log 日志，默认为 false |
| warn | Boolean | 否 | 是否输出 warn 日志，默认为 true |
| error | Boolean | 否 | 是否输出 error 日志，默认为 true |

```js
const novelPlugin = requirePlugin('novel-plugin')

novelPlugin.setLoggerConfig({
  info: true,
  debug: true,
  log: true,
  warn: true,
  error: true,
})
```

#### NovelManager

阅读器，用于对插件页进行操作管理。每个插件页对应一个阅读器实例。

```js
const novelPlugin = requirePlugin('novel-plugin')

novelPlugin.onPageLoad(params => {
  // 获取阅读器实例
  const novelManager = novelPlugin.getNovelManager(params.id)

  // do something
})
```

#### Number NovelManager.getId()

获取阅读器实例 ID。

```js
const novelManagerId = novelManager.getId()
```

#### String NovelManager.getBookId()

获取阅读器实例对应的书籍 ID。

```js
const bookId = novelManager.getBookId()
```

#### Object NovelManager.getPluginInfo()

获取阅读器信息。

返回参数结构如下：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| pluginAppid | String | 插件 AppID |
| pluginVersion | String | 插件版本号 |
| query | Object | 插件页的 query 参数对象 |

> 注意：若是从书籍分享卡片或是其他方式直接进入到插件页，开发者便可通过此接口的 query 字段获取到插件页 path 上的参数。

```js
const pluginInfo = novelManager.getPluginInfo()
```

#### Object NovelManager.getLaunchOptions()

获取启动参数，与 wx.getLaunchOptionsSync 返回值一致。

```js
const launchOptions = NovelManager.getLaunchOptions()
```

#### NovelManager.onPluginError(Function callback)

监听阅读器主动抛出的错误。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| event | String | 错误类型 |

```js
novelManager.onPluginError(res => {
  // do something
})
```

#### String NovelManager.getCustomServerParams()

获取 customServerParams 参数，含义同跳转插件页时传入的 customServerParams 参数。

```js
const customServerParams = novelManager.getCustomServerParams()
```

#### NovelManager.setCustomServerParams(Object params)

设置 customServerParams 参数，含义同跳转插件页时传入的 customServerParams 参数。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| customServerParams | String | 是 | |

```js
novelManager.setCustomServerParams({
  customServerParams: 'xxx'
})
```

#### NovelManager.setClosePluginInfo(Object params)

设置用户点击书籍介绍页导航栏返回按钮时的跳转行为，在页面栈中只有当前插件页时生效。如果当前页面栈中页面数量大于 1，则会走 navigateBack 逻辑返回上一个页面。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| url | String | 否 | 跳转的路径，如不传则默认 reLaunch 到小程序首页 |
| mode | String | 否 | 跳转方式，支持 switchTab 或 redirectTo，取决于要跳转的目标页面类型；默认值为 redirectTo |

```js
novelManager.setClosePluginInfo({
  url: '/pages/tab/index',
  mode: 'switchTab',
})
```

#### NovelManager.setLeaveReaderInfo(Object params)

设置用户点击阅读页导航栏返回按钮时的跳转行为。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| url | String | 否 | 跳转的路径，如不传则默认回到书籍介绍页；如果 mode 为 navigateBack 时可不传此字段 |
| mode | String | 否 | 跳转方式，支持 navigateBack、switchTab 或 redirectTo，取决于要跳转的目标页面类型；默认值为 redirectTo |

```js
novelManager.setLeaveReaderInfo({
  url: '/pages/tab/index',
  mode: 'switchTab',
})
```

### 2.2 分享

### NovelManager.setShareParams(Object params)

设置分享书籍时的参数。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| enable | Boolean | 否 | 是否开启分享能力，默认为 true |
| title | String | 否 | 书籍标题 |
| imageUrl | String | 否 | 书籍封面 |
| args | Object | 否 | 在分享时可携带的参数，要求 key 和 value 均为 String 类型 |

```js
// 关闭分享
novelManager.setShareParams({
  enable: false,
})

// 调整分享参数
novelManager.setShareParams({
  title: 'xxx',
  imageUrl: 'xxx',
  args: {
    a: '123',
    b: '321',
  },
})
```

### 2.3 读后推荐

读后推荐需要开发者提前设置想要推荐的小说列表，可参考[此文档](/miniprogram/dev/server/API/novel/other/api_novelreadersetrecmdnovel.md)进行前置接入。

#### NovelManager.setRecommendInfo(Object params)

设置读后推荐相关配置。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--:|:--:|:--:|:--|
| homeButton | Object | 否 | 设置用户点击"前往书城"按钮时的跳转行为 |

homeButton 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--:|:--:|:--:|:--|
| url | String | 否 | 跳转的路径；如果 mode 为 navigateBack 时可不传此字段 |
| mode | String | 否 | 跳转方式，支持 navigateTo、navigateBack、switchTab 或 redirectTo，取决于要跳转的目标页面类型；默认值为 redirectTo |

```js
novelManager.setRecommendInfo({
  homeButton: {
    url: '/pages/index/index',
    mode: 'navigateTo',
  },
})
```

#### NovelManager.setRecommendBookshelfStatus(Object params)

设置读后推荐沉浸模式中的书架状态。此状态阅读器不会做任何存储，需要开发者传入。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--:|:--:|:--:|:--|
| bookshelfStatus | Number | 是 | 当前书架状态：0 - 未添加；1 - 已添加 |
| bookId | String | 是 | 被推荐书籍的 bookId |

```js
novelManager.setRecommendBookshelfStatus({
  bookshelfStatus: 1,
  bookId: 'xxx',
})
```

#### NovelManager.onRecommendClickBookshelf(Function callback)

监听读后推荐沉浸模式中的书架按钮点击事件。此事件触发时不会改变当前书架状态，需要等用户处理完事件后手动调用 setRecommendBookshelfStatus 来更新书架状态。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| bookshelfStatus | Number | 预期书架状态：0 - 未添加；1 - 已添加 |
| bookId | String | 被推荐书籍的 bookId |

```js
novelManager.onRecommendClickBookshelf(res => {
  // do something

  // 用户处理好状态变更后，再手动更新书架状态
  novelManager.setRecommendBookshelfStatus({
    bookshelfStatus: !!res.bookshelfStatus ? 1 : 0,
    bookId: 'xxx',
  })
})
```

> 注意：当该书籍被置为"已添加到书架"的状态时，书架按钮会置为不可点击状态。

#### NovelManager.onRecommendClickContinue(Function callback)

监听读后推荐沉浸模式中的继续阅读按钮点击事件。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| bookId | String | 被推荐书籍的 bookId |

```js
novelManager.onRecommendClickContinue(res => {
  // do something

  novelManager.navigateTo({
    url: `plugin-private://wx293c4b6097a8a4d0/pages/novel/index?bookId=${params.bookId}`,
  })
})
```

> 注意：如果开发者没有监听此事件，则阅读器会将其处理为跳转到被推荐书籍的阅读页。

#### NovelManager.onRecommendClickBook(Function callback)

监听读后推荐被推荐书籍信息的点击事件。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| bookId | String | 被推荐书籍的 bookId |

```js
novelManager.onRecommendClickBook(res => {
  // do something

  novelManager.navigateTo({
    url: `plugin-private://wx293c4b6097a8a4d0/pages/novel/index?bookId=${params.bookId}`,
  })
})
```

> 注意：如果开发者没有监听此事件，则阅读器会将其处理为跳转到被推荐书籍的介绍页。

### 2.4 书架

#### NovelManager.setBookshelfStatus(Object params)

设置书架状态。此状态阅读器不会做任何存储，需要开发者传入。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| bookshelfStatus | Number | 是 | 当前书架状态：0 - 未添加；1 - 已添加 |
| showToast | Boolean | 否 | 是否在非第一次书架状态变更时展示提示，默认为 true |

```js
novelManager.setBookshelfStatus({
  bookshelfStatus: 1,
})
```

#### NovelManager.onClickBookshelf(Function callback)

监听书架按钮点击事件。此事件触发时不会改变当前书架状态，需要等用户处理完事件后手动调用 setBookshelfStatus 来更新书架状态。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--:|:--:|:--|
| bookshelfStatus | Number | 预期书架状态：0 - 未添加；1 - 已添加 |

```js
novelManager.onClickBookshelf(res => {
  // do something

  // 用户处理好状态变更后，再手动更新书架状态
  novelManager.setBookshelfStatus({
    bookshelfStatus: !!res.bookshelfStatus ? 1 : 0,
  })
})
```

### 2.5 活动运营位

#### NovelManager.setActivity(Object params)

设置"活动"按钮运营位。

![image.png](https://res.wx.qq.com/op_res/Samg6fTeRfxvyYAmlcGaa7SegUJkB8CIW2H7Z3XaehROxCYIWP-lcMveMy7D8pKge4ElIzZnjvdsqug4s7xQtA)

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--:|:--:|:--:|:--|
| url | String | 是 | 点击"活动"按钮后的跳转路径 |

```js
novelManager.setActivity({
  url: '/pages/activity/index',
})
```

### 2.6 目录

#### NovelManager.setContents(Object params)

设置目录。

此方法只会更新目录的 UI，并不会实际进行章节的解锁操作，阅读器只有拉取到章节数据后才知道该章节是否解锁，无法知悉所有章节的解锁状态。如果开发者需要在目录上展示正确的解锁状态，便可使用此接口对章节进行一次解锁状态初始化。

章节如何解锁可参考文档中下一个部分的解锁链路。目录中的章节状态会优先以该链路中后台返回的解锁状态为准，其次再取此接口中设置的状态进行显示。

> 注意：如果通过此接口设置了某章节为已解锁状态，但是进入该章节（或该章节的临近章节）后发现目录变成了未解锁状态，这种情况一般是拉取到了章节数据后发现后台返回的状态是未解锁，便会以此状态为优先来更新目录。遇到此问题可先检查下开发者前端知悉的章节状态和开发者后台存储的章节状态是否没有对齐。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--|:--|:--|
| contents | `Array<Content>` | 是 | 章节解锁状态列表 |

Content 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--|:--|:--|
| index | Number | 是 | 章节下标 |
| status | Number | 是 | 章节解锁状态：0 - 免费章节；1 - 已解锁章节；2 - 未解锁章节 |

```js
novelManager.setContents({
  contents: [
    {
      index: 0, // 第一章
      status: 0,
    },
    {
      index: 1, // 第二章
      status: 2,
    },
    {
      index: 2, // 第三章
      status: 1,
    },
  ],
})
```

### 2.7 章节解锁

#### NovelManager.setChargeWay(Object params)

设置章节解锁方式。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--|:--|:--|
| globalConfig | `SetChargeWayItem` | 否 | 针对所有章节的全局设置 |
| chapterConfigs | `Array<SetChargeWayItem>` | 否 | 按章节设置 |

SetChargeWayItem 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--|:--|:--|
| mode | Number | 否 | 解锁方式：1 - 默认值，用户点击按钮会自动拉起章节解锁组件；2 - 用于广告解锁，用户点击按钮后触发 onUserClickAdUnlock 事件；3 - 用于自定义解锁，用户点击按钮后触发 onUserClickCustomUnlock 事件；4 - 不展示解锁按钮和提示信息；5 - 用于自动唤起解锁，展示两个按钮（3s倒计时按钮和常规按钮）和提示信息，倒计时结束/用户主动点击倒计时按钮会触发 onCountdownDoneUnlock 事件，用户点击常规按钮触发 onUserClickCustomUnlock 事件；6 - 用于按字数插入解锁提示，展示两个按钮（3s倒计时按钮和常规按钮）和提示信息，倒计时结束/用户主动点击倒计时按钮会触发 onCountdownDoneUnlock 事件，用户点击常规按钮触发 onUserClickCustomUnlock 事件 |
| buttonText | String | 否 | 未解锁时显示的按钮文案，最多 2 个字，在 mode: 5 用于常规按钮文案，此时最多 12 个字 |
| countdownInProgressButtonText | String | 否 | 用于 mode: 5 ，正在进行中的倒计时按钮文案，文案必须包含 {{countdown}}，最多 26 个字，eg: 倒计时还剩 {{countdown}} 秒 |
| countdownEndedButtonText | String | 否 | 用于 mode: 5 ，倒计时已结束按钮文案，最多 12 个字，eg: 倒计时已结束 |
| tip | String | 否 | 未解锁时显示的提示信息，最多 18 个字 |
| showButton | Boolean | 否 | 是否展示解锁按钮，默认非 iOS 侧为 true，iOS 侧为 false。即 iOS 侧默认只展示提示信息不展示解锁按钮 |
| chapterIndex | Number | 是 | 章节下标，globalConfig 中不用传该参数 |

> 注意：解锁按钮和提示信息的展示是互斥的，两者只能展示其一。

```js
novelManager.setChargeWay({
  globalConfig: {
    mode: 2,
    buttonText: '解锁',
    tip: '暂不支持继续阅读',
  },
  chapterConfigs: [
    {
      chapterIndex: 19, // 第二十章
      mode: 1,
      buttonText: '购买',
      tip: 'xxx之后可继续阅读',
    },
  ]
})
```

#### NovelManager.paymentCompleted(Object params)

通知阅读器解锁成功，阅读器会刷新当前章节状态。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--:|:--:|:--:|:--|
| chapterIndex | Number | 否 | 解锁完成的章节下标。如果是在章节解锁组件拉起时调用 paymentCompleted，则无需传入此字段，会默认取该章节解锁组件对应的章节下标来处理 |

```js
novelManager.paymentCompleted()
```

#### NovelManager.openChargeDialog(Object params)

拉起章节解锁组件。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--|:--|:--|
| chapterIndex | Number | 否 | 章节下标 |
| chapterId | String | 否 | 章节 ID |
| originalId | String | 否 | 章节原始 ID，即开发者上传章节时传入的 original_id |

> 注意：chapterIndex、chapterId、originalId 传入一个即可，若都不传入则拉起当前章节的章节解锁组件。

```js
novelManager.openChargeDialog({
  chapterIndex: 10, // 第十一章
})
```

#### NovelManager.closeChargeDialog()

关闭章节解锁组件。

```js
novelManager.closeChargeDialog()
```

#### NovelManager.onUserClickAdUnlock(Function callback)

当调用 setChargeWay 将章节解锁方式 mode 设置为 2 时，点击解锁按钮会触发此事件。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--:|:--:|:--|
| chapterIndex | Number | 章节下标 |

```js
novelManager.onUserClickAdUnlock(res => {
  // do something
})
```

#### NovelManager.onUserClickCustomUnlock(Function callback)

当调用 setChargeWay 将章节解锁方式 mode 设置为 3 时，点击解锁按钮会触发此事件；mode 设置为 5 时，点击常规按钮会触发此事件。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--|:--|:--|
| chapterIndex | Number | 章节下标 |

```js
novelManager.onUserClickCustomUnlock(res => {
  // do something
})
```

#### NovelManager.onCountdownDoneUnlock(Function callback)

当调用 setChargeWay 将章节解锁方式 mode 设置为 5 时，倒计时按钮计时结束/用户主动点击倒计时按钮会触发此事件。

事件监听回调函数参数如下：

| 参数 | 类型 | 描述 |
|:--|:--|:--|
| chapterIndex | Number | 章节下标 |

```js
novelManager.onCountdownDoneUnlock(res => {
  // do something
})
```

### 2.8 事件

#### NovelManager.onUserTriggerEvent(Function callback)

监听用户行为事件。目前支持的事件如下：

| 事件 ID | 触发时机 |
|:--|:--|
| start_read | 开始阅读书籍时 |
| leave_readpage | 用户离开阅读页时 |
| read_error | 内容发生报错时 |
| change_chapter | 切换章节时 |
| change_page | 翻页时 |
| open_menu | 唤起一级菜单时 |
| close_menu | 轻触收起一级菜单时 |
| open_content | 唤起目录页时 |
| close_content | 收起目录页时 |
| open_setting | 唤起设置面板 |
| close_setting | 收起设置面板 |
| expose_introduction | 书籍介绍页曝光 |
| click_addbookshelf | 点击"加入书架" |
| click_startread | 点击"开始阅读" |
| leave_introduction | 离开当前页 |
| get_chapter | 拉取章节数据结束 |
| page_show | 插件页 onShow 生命周期 |
| page_hide | 插件页 onHide 生命周期 |
| page_unload | 插件页 onUnload 生命周期 |
| change_fontsize | 调整字号时 |
| change_flipmode | 调整翻页模式时 |
| change_background | 调整背景色或夜间模式时 |
| ad_error | 广告报错时 |
| close_ad | 关闭广告时 |
| leave_chapter | 离开章节时 |
| click_listen | 点击听书按钮时 |
| audio_start | 音频播放 |
| audio_pause | 音频暂停 |
| audio_error | 音频报错 |
| audio_stop | 停止播放音频 |
| audio_end | 音频自然播放结束 |
| share | 点击分享 |
| add_to_favorites | 点击收藏 |

> 注意：插件页创建时的 page_show 事件开发者无法监听到，因为 onPageLoad 的触发时间会晚于插件页创建时的 onShow 生命周期

事件基础参数如下：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| event_id | String |	事件 ID |
| appid | String | 小程序 AppID |
| appstate | String | 小程序版本：0 - 正式版；1 - 体验版；2 - 开发版 |
| book_id | String | 书籍 ID |
| title | String | 书籍名 |
| chapter_index | Number | 章节下标 |
| chapter_title | String | 章节标题，change_chapter 事件触发后才会返回 |
| first_category_id | Number | 一级类目 ID |
| first_category_name | String | 一级类目名称 |
| second_category_id | Number | 二级类目 ID |
| second_category_name | String | 二级类目名称 |
| third_category_id | Number | 三级类目 ID |
| third_category_name | String | 三级类目名称 |
| total_count | Number | 总字数 |
| total_count_chapter | Number | 总章节数 |
| complete_status_name | String | 完结状态 |
| font_index | Number | 字体分级 |
| background_index | Number | 背景样式 |
| flip_mode | Number | 翻页方式：TURN_PAGE_WAY_SWIPE - 覆盖翻页；TURN_PAGE_WAY_MOVE - 平移翻页；TURN_PAGE_WAY_SCROLL - 上下翻页 |

另有部分参数只在特定事件下支持：

| 参数 | 类型 | 描述 | 支持该参数的事件 ID |
|:--|:--:|:--|:--|
| delay_time | Number | 加载时间 | start_read |
| pay_status | Number | 章节状态：0 - 免费章节；1 - 已解锁；2 - 未解锁 | start_read、get_chapter |
| read_time | Number | 阅读时长 | leave_readpage、change_chapter、change_page、page_hide、page_unload、leave_chapter |
| read_time | Number | 听书时长 | audio_pause、audio_error、audio_stop、audio_end |
| type | Number | 离开方式：1 - 退出页面；2 - 退后台；3 - 进入到阅读页/介绍页 | leave_introduction、leave_readpage |
| type | Number | 离开方式：1 - 退出页面；2 - 退后台；3 - 页面 onHide；4 - 页面 onUnload；5 - 回到介绍页 | leave_chapter |
| type | Number | 离开方式：1 - 点击开始音频播放；2 - 点击停止音频播放 | click_listen |
| error | String | 错误描述 | read_error |
| error_type | Number | 错误码 | read_error |
| slide_type | Number | 切换方式：1 - 滑动；2 - 选集 | change_chapter |
| intro | String | 书本简介 | expose_introduction |
| is_night_mode | Boolean | 是否夜间模式 | start_read、change_background |
| read_progress | Number | 当前章节阅读进度 | page_hide、page_unload、leave_chapter |
| read_progress | Number | 当前音频播放进度 | audio_pause、audio_error、audio_stop、audio_end |
| adType | Number | 广告类型，即 setAdBlock 时传入的 type | ad_error、close_ad |
| key | String | 广告的 key，在 setAdBlock 时可传入 | ad_error、close_ad |
| show_listen | Boolean | 是否展示听书按钮 | start_read |
| audio_ready | Number | 1 - 已经生成音频；2 - 未生成音频 | click_listen |
| duration | Number | 音频长度 | audio_start、audio_pause、audio_error、audio_stop、audio_end |
| current_time | Number | 音频当前播放时间 | audio_pause、audio_error、audio_stop、audio_end |

注意：

1. 因为有些字段在不同事件中含义可能不一样，所以该字段可能会在列表中出现多次。
2. chapter_index 等字段会以页面实际停留位置为准；例如开启听书时会默认跟随音频进度进行翻页，此时如果用户手动操作书籍后则会停留在该页面，后续音频相关事件中的 chapter_index 等字段则会取当前停留页面所在章节的信息，而不是音频实际播放到的位置。

```js
novelManager.onUserTriggerEvent(res => {
  console.log('onUserTriggerEvent', res.event_id, res)
})
```

#### NovelManager.onCustomEvent(String name, Function callback)

监听开发者自定义事件。

```js
novelManager.onCustomEvent('customEvent', res => {
  // do something
})
```

#### NovelManager.emitCustomEvent(String name, Object params)

触发开发者自定义事件。

```js
novelManager.emitCustomEvent('customEvent', {
  a: '123',
  b: 321,
})
```

### 2.9 自定义段落

#### NovelManager.setParagraphBlock(Object params)

在正文中插入自定义段落。

![image.png](https://res.wx.qq.com/op_res/B4BBsJ6SWF10XehrOkDZP6q4tL96A-BA1LDAlksIovwFgQAmh-sazqY3IR0XzpZtQjVcAw-60q9u3Wrtub-DAQ)

和章节解锁组件一样，需要先编写一个自定义组件 paragraph-block，该组件和普通自定义组件编写规则一致，然后在 app.json 中将此组件作为抽象节点添加到插件设置中：

```json
{
  "plugins": {
    "novel-plugin": {
      "version": "latest",
      "provider": "wx293c4b6097a8a4d0",
      "genericsImplementation": {
        "novel": {
          "charge-dialog": "path/to/component",
          "paragraph-block": "path/to/component2",
        }
      }
    }
  }
}
```

阅读器插件会传入以下属性给该组件：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| novelManagerId | Number | 阅读器实例 ID |
| chapterIndex | Number | 章节下标 |
| ext | String | 透传参数 |

然后再调用此接口可以将 paragraph-block 作为自定义段落插入到文中，params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| globalConfigs | `Array<ParagraphBlock>` | 否 | 针对所有章节的全局设置 |
| chapterConfigs | `Array<ChapterParagraphBlock>` | 否 | 按章节设置 |

ChapterParagraphBlock 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| chapterIndex | Number | 是 | 章节下标 |
| blocks | `Array<ParagraphBlock>` | 是 | 自定义段落设置 |

ParagraphBlock 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| height | Number | 是 | 自定义段落高度，单位 px |
| position | Number | 是 | 自定义段落位置，0 表示在标题前，1 表示在第一段文字前，以此类推 |
| ext | String | 是 | 透传给自定义组件的参数 |
| key | String | 否 | 自定义段落唯一 ID，用于阅读器内部的 wx:for 列表更新；若不传则阅读器会自动生成一个 |
| disableContinueReading | Boolean | 否 | 若为 true，则用户在阅读到此段落时无法阅读当前章节内该段落之后的内容；默认为 false |
| fillContentBefore | Boolean | 否 | 若为 true，则当前页空间不足以容纳该段落时，会先将后面段落内容尽可能地填充到当前页，然后在下一页进行该段落的插入；默认为 false |
| onlyRenderInCurrentPage | Boolean | 否 | 若为 true，则在平移翻页/覆盖翻页时，此自定义段落只会在当前页进入动画结束后渲染；默认为 false，即在过渡页中也会进行渲染，以优化用户的切页体验 |

使用此接口时有以下要点必须注意：

1. 自定义段落的高度是必须传入的，阅读器的分页计算需要明确知道每个段落的高度。
2. 此接口每次调用都会触发重新排版和渲染，会对性能和体验有一定影响，请勿频繁调用该方法。
3. 每章最多只能插入 10 个自定义段落。
4. 全局设置会和按章节设置混在一起进行展示。

```js
novelManager.setParagraphBlock({
  globalConfigs: [ // 在这里设置的是全局设置 会在所有章节生效
    {
      height: 300,
      position: 1,
      ext: 'global',
    },
  ],
  chapterConfigs: [
    {
      chapterIndex: 0, // 第一章
      blocks: [
        {
          height: 100,
          position: 1,
          ext: 'chapter',
        }
      ],
    },
  ]
})
```

### 2.10 广告

#### NovelManager.setAdBlock(Object params)

在正文中插入广告段落。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| chapterConfigs | `Array<ChapterAdBlock>` | 否 | 按章节设置 |

ChapterAdBlock 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| chapterIndex | Number | 是 | 章节下标 |
| blocks | `Array<AdBlock>` | 是 | 自定义段落设置 |

AdBlock 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| type | Number | 否 | 广告类型：1 - 强制观看广告；2 - banner 广告；3 - 书签广告；默认值为 1 |
| position | Number | 否 | type = 1 时必填。广告段落位置，0 表示在标题前，1 表示在第一段文字前，以此类推。此位置并不一定精确的位置，如强制观看广告类型，会将广告前一页面进行填充（如自定义段落的 fillContentBefore 设置），相当于会将广告段落按一定程度往后挪动 |
| startPosition | Number | 否 | type = 2 时选填，广告开始段落位置，计算方式同 position |
| endPosition | Number | 否 | type = 2 时选填，广告结束段落位置，计算方式同 position |
| duration | Number | 否 | type = 1 时选填，强制观看广告要求强制观看的时长，单位为秒，范围为 0 - 30，默认值为 6 |
| unitId | String | 是 | 原生模板广告的广告单元 ID，参考 [ad-custom](/miniprogram/dev/component/ad-custom.md) 组件文档 |
| key | String | 否 | 该广告的 key，用于标记某一段广告 |

目前广告段落只支持一种广告：

| 广告类型 | 描述 |
|:--|:--|
| 强制观看广告 | 默认占用一页的高度，用户需要观看广告足够时间后才能向后翻页/滚动，只有广告出现在页面中时才会开始计时。若用户观看广告符合条件后，记录会保存在本地，在一定时间内该广告都将不再要求强制观看时间 |
| banner 广告 | 默认显示在阅读页底部，用户可关闭；广告关闭只在当前页面生命周期内有效；若传入 startPosition 和 endPosition 时会在段落区间所在页面出现广告，若不传则表示整章都出现广告 |
| 书签广告 | 基本逻辑同`强制观看广告`，只有样式有区别 |

注意事项：

1. 广告以 `{bookId}-{chapterId}-{position}-{type}` 组合为唯一 ID，此 ID 不同即视为不同广告。
2. 如果 banner 广告和强制观看广告/书签广告同时出现的话，则会优先展示强制观看广告/书签广告而隐藏 banner 广告。

```js
novelManager.setAdBlock({
  chapterConfigs: [
    {
      chapterIndex: 0, // 第一章
      blocks: [
        {
          // 强制观看广告
          type: 1,
          position: 10,
          duration: 10,
          unitId: 'adunit-xxxxxxxxx',
        },
      ],
    },
    {
      chapterIndex: 1, // 第二章
      blocks: [
        {
          // banner 广告，部分页面出现广告
          type: 2,
          startPosition: 2,
          endPosition: 20,
          unitId: 'adunit-xxxxxxxxx',
        },
      ],
    },
    {
      chapterIndex: 2, // 第三章
      blocks: [
        {
          // banner 广告，整章出现广告
          type: 2,
          unitId: 'adunit-xxxxxxxxx',
        },
      ],
    },
  ],
})
```

#### NovelManager.setAdInfoForWordCountBasedAd(Object params)

在正文中按固定字数间隔插入广告段落。

params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| startWordCount | Number | 是 | 开始插入广告的起始字数要求，最低 3000 字 |
| gapWordCount | Number | 是 | 插入广告的间隔字数要求，最低 1000 字 |

注意事项：

1. 如果同时调用`setAdInfoForWordCountBasedAd`和`setAdBlock`，则以最后调用的为准。
2. 章节解锁进度根据解锁次数确定，如果对同一个用户使用不同的参数，可能导致用户需要重复解锁已经阅读过的内容。
3. 需要结合`setChargeWay` mode=6 使用。
4. 通过调用`paymentCompleted`通知插件解锁成功。

```js
novelManager.setAdInfoForWordCountBasedAd({
  startWordCount: 100,
  gapWordCount: 100,
})
novelManager.setChargeWay({
  globalConfig: {
    mode: 6,
    tip: '',
    showButton: true,
    buttonText: '',
    countdownInProgressButtonText: '{{countdown}}',
    countdownEndedButtonText: '',
  },
  chapterConfigs: [],
})
```

### 2.11 全屏组件

#### NovelManager.setFullScreenComponentStatus(Object params)

插入全屏组件。

> 注意：全屏组件会直接渲染在阅读器之上，会对用户阅读体验有影响，且会截获用户的点击等事件，请谨慎使用。

![image.png](https://res.wx.qq.com/op_res/B4BBsJ6SWF10XehrOkDZP0xljtegmXJrLPRTylrR95FZMIjvixDIh9NNPLFuE62-cj_r4eh7EQTjnFQudAgQyA)

和章节解锁组件一样，需要先编写一个自定义组件 full-screen，该组件和普通自定义组件编写规则一致，然后在 app.json 中将此组件作为抽象节点添加到插件设置中：

```json
{
  "plugins": {
    "novel-plugin": {
      "version": "latest",
      "provider": "wx293c4b6097a8a4d0",
      "genericsImplementation": {
        "novel": {
          "charge-dialog": "path/to/component",
          "full-screen": "path/to/component2",
        }
      }
    }
  }
}
```

阅读器插件会传入以下属性给该组件：

| 参数 | 类型 | 描述 |
|:--|:--:|:--|
| novelManagerId | Number | 阅读器实例 ID |
| chapterIndex | Number | 章节下标 |

然后再调用此接口可以将 full-screen 渲染在阅读器之上，params 参数结构如下：

| 参数 | 类型 | 必填 | 描述 |
|:--|:--:|:--:|:--|
| show | Boolean | 是 | 展示或销毁全屏组件 |

```js
novelManager.setFullScreenComponentStatus({
  show: true,
})
```

### 2.12 跳转小程序

#### NovelManager.navigateToMiniProgram(Object params)

接口含义和参数同 wx.navigateToMiniProgram。

### 2.13 路由

#### NovelManager.reLaunch(Object params)

接口含义和参数同 wx.reLaunch。

#### NovelManager.switchTab(Object params)

接口含义和参数同 wx.switchTab。

#### NovelManager.redirectTo(Object params)

接口含义和参数同 wx.redirectTo。

#### NovelManager.navigateTo(Object params)

接口含义和参数同 wx.navigateTo。

#### NovelManager.navigateBack(Object params)

接口含义和参数同 wx.navigateBack。

### 2.14 常见问题

拉取书籍内容时，跟付费链路异常有关的错误码：

| 错误码 | 原因 |
|:--:|:--|
| 80002 | 开发者未配置消息推送 |
| 80003 | 消息推送链路有问题 |
| 80004 | 开发者回包格式有问题（可能返回 xml 格式） |
| 80005 | 回包的 ErrCode 非 0，可能是开发者得业务逻辑有问题 |

### 2.15 前置拦截返回按钮触发

### NovelManager.oTapBeforeBack
```js
  NovelManager.oTapBeforeBack((params) => {
    Logger.log('oTapBeforeBack :', params)
    return new Promise((resolve,reject) => {
      wx.showModal({
        title: '提示',
        content: '是否要退出阅读器',
        success (res) {
          if (res.confirm) {
            resolve('用户点击确定，允许退出')
          } else if (res.cancel) {
            reject('用户点击取消，不允许退出')
          }
        },
      })

    })
  })
```

## 三、附录

### 3.1 常见问题

1. 关于 lazyCodeLoading 的注意事项：如果开启了 lazyCodeLoading 功能，需要注意在开发者工具中进行调试时可能会遇到 bug。在开发者工具中，需要先关闭以下设置：

```json
{
  "lazyCodeLoading":"requiredComponents"
}
```

2. 最低基础库版本要求：`2.30.1`

### 3.2 接入咨询

如若遇到接入问题，可通过[微信开放社区](https://developers.weixin.qq.com/community/minihome/mixflow/3631365851346911237)咨询微信团队。

### 更新日志

[查看更新日志](novel-changelog)
