在uniapp微信小程序开发中使用官方阅读器插件

在uniapp微信小程序开发中使用官方阅读器插件

  1. 小说前端 🧩
  2. 5 months ago
  3. 13 min read

2024年6月份上架小说类目微信小程序时,发现直接被拒审,原因是必须引用官方的阅读器插件。

uniapp-mini-program-reader

但是查看站内信什么的都没找到相关提示,只在小程序【设置】-【第三方设置】-【插件管理】中看到了【阅读器】插件的一个简要开发文档。

uniapp-mini-program-reader

那怎么办呢?去社区搜索吧,从其他开发小伙伴的报错反馈中,找到了官方版本企业微信文档的开发指引文档,有一个简短的原生引入demo,和小说管理平台及官方阅读器插件开放的API。

uniapp中如何使用

引入第三方插件

微信小程序原生引入

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

对应到uniapp项目中,就是在manifest.jsonmp-weixin结构下引入微信小程序特有的第三方插件。 截止到目前[2024-07-01],官方的阅读器插件开放了3个自定义组件的接口,其中只有charge-dialog自定义充值区组件是稳定版本支持的,其他两个组件是dev版本支持属性。

// manifest.json
{
    // ...
    "mp-weixin" : {
        // ...
        "plugins" : {
            "novel-plugin" : {
                "version" : "latest",
                "provider" : "wx293c4b6097a8a4d0",
                "genericsImplementation" : {
                    "novel" : {
                        "charge-dialog" : "/wxcomponents/charge-dialog/index",
                        "paragraph-block" : "/wxcomponents/paragraph-block/index",
                        "full-screen" : "/wxcomponents/full-screen/index"
                    }
                }
            }
        }
    }
}

uniapp-mini-program-reader

我们接下来进行自定义组件的创建。

自定义组件

uniapp项目和原生项目的区别在这里尤为明显,我在实践过程中才发现这里要满足官方插件引入的自定义组件要用原生小程序方式来开发。

也就是说: uniapp方式嵌套官方阅读器插件,微信小程序原生开发自定义组件嵌进官方阅读器组件。

在uniapp项目结构中,我们习惯于在根目录创建wxcomponents目录用来存放微信小程序专有特性的组件。

按照官方文档开放的API来看,现在可以嵌入3个自定义组件:

  • charge-dialog 自定义充值区,展示形式:拉起半屏最高不超过75%的高度。
  • paragraph-block 文中开放区域,展示形式:嵌入在正文内容之间。配合novelManager.setParagraphBlock()方法服用,可以设置组件高度以及在第几个段落前展示。
  • full-screen全屏开放区域,展示形式:脱离正文内容,悬浮在正文内容之上。配合novelManager.setFullScreenComponentStatus()方法服用,可以设置全屏组件的显示状态。

由于后两个组件还在dev阶段,需求没那么强烈,就先实现稳定版本的charge-dialog充值组件。

创建charge-dialog组件,先来复习一下微信小程序的原生开发要包含.js/.json/.wxml/.wxss四大金刚文件,简单的对应关系是:

  • index.js对应uniappscript中基础变量/生命周期和函数方法的定义
  • index.json对应uniappcomponents中组件引入的声明
  • index.wxml对应uniapptemplate中DOM元素的定义
  • index.wxss对应uniappcss中布局样式的定义

主要逻辑

1、插件声明

/wxcomponents/charge-dialog/index.js中引入配置中声明的novel-plugin,进行全局的交互。

const novelPlugin = requirePlugin("novel-plugin");
let novelManager = null;

Component({});

2、props参数的接收

根据官方文档说明,自定义组件可以接收到父组件传递的4个properties参数,novelManagerIdnovelManager的句柄ID,bookId是MP小说管理平台的书籍ID,chapterId是MP小说管理平台生成的章节ID,chapterIndex是从0[第一章]开始的章节索引下标。


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

3、novelManager的监听和主逻辑处理

微信小程序的一些原生概念

  • observers是数据监听器,相当于watch来监听数据的变化。
  • lifetimes定义组件的生命周期。
  • attached()生命周期方法,是在组件实例进入页面节点树时执行,基本等同于mounted()的节点。

在这里,我使用observers监听了novelManagerId值的变化,更新novelManager管理器,然后在attached()中使用emitCustomEventonCustomEvent进行自定义事件的触发监听来获取充值商品列表并赋值到变量。

Component({

    lifetimes: {
        attached() {
            novelManager.emitCustomEvent('event', { action: 'charge' });
            novelManager.onCustomEvent('productList', params => {
                this.setData({
                    productList: params.productList
                })
            });
        }
    },
    observers: {
        'novelManagerId': function(id) {
            novelManager = novelPlugin.getNovelManager(id);
        }
    }
})

4、其他API方法的调用

同样的,我们也可以在methods中调用novelManager的其他一些API方法。

比如可以通过novelManager.getCustomServerParams()方法查询到我们自己在其他页面组件中设置的透传给服务端的自定义内容,可以在充值完成后调用novelManager.paymentCompleted()方法来通知官方插件付费成功。

为什么需要在自定义组件中写一堆自定义事件呢?这些事件又是在哪里使用呢?

原生实现的我不太清楚,但是我目前手上这个项目,牵扯到服务端API的加密请求/数据存取都通过uniapp封装实现了,因此我如果不想再使用微信原生实现一遍的话,就需要emit去触发uniapp实现的方法。

这,就又回到了官方文档所述的插件接入的初始化流程。

App.vue初始化接入

微信小程序原生实现的话,初始化就在app.js中,具体可查阅官方文档。uniapp的实现呢,就是在App.vue中。

1、判断环境,引入配置文件中声明的阅读器插件。

// #ifdef MP-WEIXIN
const novelPlugin = requirePlugin("novel-plugin");
// #endif

2、在methods中定义阅读器插件加载的方法onNovelPluginLoad

onNovelPluginLoad方法接收阅读插件传入的值,然后在方法内部定义对插件的初始化赋值及更新操作。

methods: {
    onNovelPluginLoad(data) {
        // 获取novelManager实例
        const novelManager = novelPlugin.getNovelManager(data.id)

        // 设置用户关闭插件/小说详情页点击返回键后的跳转路径
        novelManager.setClosePluginInfo({
            mode: 'switchTab',
            url: '/pages/index/index'
        })

        // 监听用户点击添加/移除书架的事件并更新状态,如果记得处理服务端书架操作状态
        novelManager.onClickBookshelf((params) => {
            novelManager.setBookshelfStatus({
                bookshelfStatus: status,
            })
        })

        // 监听自定义事件,将服务端返回的数据通过emit交互回自定义子组件
        novelManager.onCustomEvent('event', async (params) => {
            const productList = await $novel.getChargeProduct()
            novelManager.emitCustomEvent('productList', {
                productList
            });
        })
    }
}

3、在应用启动监听函数onLaunch()中订阅小程序初始化成功的事件

onLaunch(){
    // #ifdef MP-WEIXIN
    // 订阅小程序初始化成功的事件
    novelPlugin.onPageLoad(this.onNovelPluginLoad)
    // #endif
}

在页面中跳转使用插件

uniapp的官方说明

这里可以参考下uniapp小程序专题的使用小程序插件教程。

在页面内使用插件内包含的组件需要在pages.json内对应页面的style节点下配置对应平台的usingComponentsusingSwanComponents[百度智能小程序字段],示例如下:

"hello-component": "plugin://myPlugin/hello-component"为例,key(冒号前的hello-component)为在页面内使用的组件名称。

value分为三段,plugin为协议(在百度小程序内为dynamicLib),myPlugin为插件名称即引入插件时的名称,hello-component为插件暴露的组件名称

// pages.json微信小程序
{
  "path": "pages/index/index",
  "style": {
    "mp-weixin": {
      "usingComponents": {
        "hello-component": "plugin://myPlugin/hello-component"
      }
    }
  }
}

在页面中使用配置的插件:

// <!-- 微信小程序和支付宝小程序 -->
<navigator url="plugin://myPlugin/hello-component">
  Go to pages/hello-page!
</navigator>

但是请注意,这里说的是要在页面中使用插件,但是我们是跳转到插件页面,不是在页面中使用。

阅读器插件的官方文档

官方文档中关于跳转插件页的示例如下:

wx.redirectTo({
    url: "plugin-private://wx293c4b6097a8a4dQ/pages/novel/index?bookId=xxx"
});

所以我们应该怎么做呢?

实现探索

  • pages.json中需要使用阅读器插件的页面中声明我们要嵌进阅读器插件的自定义组件
  • 在页面中进行插件的路由跳转

示例如下:

假设我们需要在pages/novel/novel页面中跳转去插件页面,就先在pages.json的对应path配置声明自定义组件路径,然后在pages/novel/novel页面中跳转。

// pages.json声明
{
    "pages": [{
        "path": "pages/novel/novel",
        "style": {
            "navigationBarTitleText": "",
            "mp-weixin": {
                "usingComponents": {
                    "charge-dialog": "/wxcomponents/charge-dialog/index",
                    "paragraph-block": "/wxcomponents/paragraph-block/index",
                    "full-screen": "/wxcomponents/full-screen/index"
                }
            }
        }
    }]
}

pages/novel/novel.vue页面的<script>模块中跳转的不完整示例如下。

export default{
    methods: {
        toNovelDetail() {
            const customServerParams = '123';
            const { bookId, chapterIndex } = this;
            const url = `plugin-private://wx293c4b6097a8a4d0/pages/novel/index?bookId=${bookId}&chapterIndex=${chapterIndex}&blockUnpaidScroll=1&customServerParams=${customServerParams}`
            
            uni.redirectTo({
                url
            })

        }
    }
}

好啦,现在你已经可以继续快乐的玩耍了,慢着!常见问题还是要说一下的。

常见问题

1、按照上述流程,接入阅读器插件后为什么跳转就是空白页?

神说:看文档需要看完整的。

消息推送虽然放在了文档最后,但整个阅读器的主要流程都要靠它。阅读器插件每次打开小说的时候,都会通过消息推送去和我们的服务端进行交互,询问章节的解锁状态,如果服务端不回复,那整个插件就罢工啦。

2、短篇小说只有2个章节,章节付费状态的数组要怎么设置?

首先要记住,章节索引是从0开始的。

其次,如果每次返回的是一个章节的状态,StartChapterIndexEndChapterIndex设置成一样的就行。

3、消息推送选用了【安全模式】加密,回包一直不正确?

这里稍微有点坑,官方文档没有标明,回包数据是明文模式的。

也就是说,加密模式只决定开发者服务器【我们】接收到微信推送消息的模式,但是【我们】回包给微信时是采用的明文模式。

{
    "ErrCode": 0,
    "ErrMsg": "",
    "ChapterPerms": [
        {
            "StartChapterIndex": 0,
            "EndChapterIndex": 0,
            "Perm": 0
        },{
            "StartChapterIndex": 1,
            "EndChapterIndex": 1,
            "Perm": 2
        }
    ]
}
前端 小程序 uniapp