uni-app(Vue2)抖音小程序开发中接入官方小说阅读器插件

uni-app(Vue2)抖音小程序开发中接入官方小说阅读器插件

  1. 小说前端 🧩
  2. 2025-03-21 12:28
  3. 12 min read

抖音小说阅读器插件依赖于行业SDK权限,因此一定要先在代码配置行业SDK权限。如果尚未配置过,可参考uni-app(Vue2)编译为抖音小程序时,自动生成package.json文件配置行业SDK权限

小程序服务类目为「文娱-文娱-在线阅读」无需申请,会自动开通小说资源内容库接入依赖的接口权限。

能力申请

自定义页面结构

申请入口:抖音开放平台-控制台-能力-互动能力-容器界面-自定义页面结构

阅读器有自定义导航栏,需要开启【自定义页面结构】后在页面style中设置"navigationStyle": "custom"支持沉浸式阅读,如果暂时不满足能力开通条件,可以使用"transparentTitle": "always"设置导航栏透明来临时避免双重导航栏UI情况。

添加到桌面

申请入口:抖音开放平台-控制台-能力-互动能力-系统能力-添加到桌面

如果要在阅读器中展示【加桌按钮】,需要提前申请开通此能力。

阅读器页面

阅读器接入的硬性规定:阅读器页面路径必须是/pages/novel_plugin/index。创建页面后在pages.json绑定行业SDK,配置其他可选参数,示例如下:

// pages.json
// #ifdef MP-TOUTIAO 
{
    "path": "pages/novel_plugin/index",
    "style": {
        "navigationBarTitleText": "...",
        "navigationStyle": "custom",
        // "transparentTitle": "always",
        "mp-toutiao": {
            "extends": "ext://industry/novel",
            "isPageExtension": true,
            "pluginConfig": {
                "addBookshelf": true,
                "addDesktopIcon": true, 
                "preventScreenRecorder": "never",
                "pageTurnMode": "horizontalSlide",
                "refreshLockChapter": true
            },
            "usingComponents": {
                "payModal": "/components/novel-modal/pay"
            }
        }
    }
},
// #endif

页面样式

阅读器自带了自定义导航栏,因此需要开通【自定义页面结构】能力,配置"navigationStyle": "custom"自定义导航栏来支持沉浸式阅读,避免双重导航栏UI情况。

如果小程序暂不满足能力申请条件,临时解决方案:配置"navigationStyle": "default", "transparentTitle": "always",设置导航栏透明也可以【注意:IDE显示可能会有错乱】。

绑定行业能力

绑定小说行业能力是固定写法"extends": "ext://industry/novel", "isPageExtension": true,没有什么转圜余地。

小说插件配置项

pluginConfig对应插件配置项。

  • 配置"addBookshelf": true展示加入书架按钮,boolean类型,默认值true

  • 配置"addDesktopIcon": true展示加桌按钮,boolean类型,默认值false

  • 配置"preventScreenRecorder": "never"是否防录屏,iOS 3160版本以上生效,枚举值always|never,默认值never

  • 配置"pageTurnMode": "horizontalSlide"新用户首次进入阅读器时默认翻页模式为左右滑动翻页,枚举值verticalScroll|horizontalSlide对应上下滑动翻页|左右滑动翻页,默认值verticalScroll

  • 配置"refreshLockChapter": true解锁状态变更时强制刷新章节内容,boolean类型,默认值false

注意事项

  • 加桌按钮展示addDesktopIcon依赖于互动能力能力,开通成功后才可以正常使用。申请入口:抖音开放平台-控制台-互动能力-系统能力-添加到桌面。

  • 章节内容刷新refreshLockChapter,官方说法如下:

阅读器出于性能考量,传入章节文本内容后会做缓存,解锁状态变更时,不会重新拉取文本内容;因此增加一个强制刷新开关,开启章节强制刷新后,当章节解锁状态发生变更,即从未解锁变为解锁状态,会重新调用queryChapterInfo查询章节内容,即使用后一次查询的章节文本内容,覆盖之前传入的章节文本,并重新渲染;

uniapp-douyin-novel-reader

页面插槽组件的引用

usingComponents对应页面插槽组件的定义。

截止目前(2025-03-20)官方只支持两种组件:自定义支付弹窗组件payModal和自定义挂件点击拉起的自定义面板组件floatModal

由于uniapp编译后组件名都是采用的连接线格式(这里组件名是固定的驼峰格式),所以我们需要在这里把页面中引入过的或者easycom自动引入的组件再重新引入一下,否则目前调用组件会显示空白的,也就是阅读器页面找不到组件。

阅读器实现

页面完整代码可以参考官方文档接入示例代码,示例会有更新,一定要记得去官方文档查看最新示例代码。

和常规uniapp的Vue2页面代码规范稍有不同,就是把datamethods和页面生命周期函数的定义都放在一个对象变量NovelExtensionOption内部,然后传递给tt.NovelExtension(NovelExtensionOption),大致形式如下:

import payModal from "@/components/novel-modal/pay.vue"
const {
    NovelExtension,
    getNovelManager
} = tt;
let novelManage = null;

const NovelExtensionOption = {
    data() {
        return {}
    },
    onLoad(options) {
        novelManage = getNovelManager()
        novelManage.onNovelError((e) => {
            //...
        })

        novelManage.onClickUnlockPrimaryButton((res) => {
            console.log('解锁按钮1触发', res)
        })

        uni.$on('payModalEvent', this.handlePayModalEvent)
    },
    onReady(){},
    onShow(){},
    onHide(){},
    onUnload(){
        uni.$off('payModalEvent', this.handlePayModalEvent)
    },
    methods: {
        queryContentsInfo(params) {
            const { bookId, page, pageSize} = params;
            return {
                chapterList: [
                    {"chapterId": "1", "status": 0}
                ]
            }
        },
        queryLockModalText(chapterId) {
            return {
                title: "弹窗标题",
                primary: "弹窗第一个按钮内容"
            }
        },
        // 其他使用的方法函数
        handlePayModalEvent(params) {},
        //...
    }
}

NovelExtension(NovelExtensionOption)

export default {
    ...NovelExtensionOption,
    components: {
        payModal
    }
}

支付弹窗组件

组件的实现没什么区别,主要有两点需要注意。

参数接收

props中定义三个参数bookIdchapterIdparams,其中bookIdchapterId是阅读器页面固定传入的抖音小说资源库对应的ID,params是在监听支付弹窗点击时回调函数showPayModal传递的对象参数。

props: {
    bookId: "",
    chapterId: "",
    params: {
        type: Object,
        default(){
            return {}
        }
    },
}

事件传递

正常组件调用时我们可以定义事件后在组件内通过this.$emit('change', params)传递给页面,但是抖音阅读器页面是不支持这种形式的。我们只能通过全局事件的传递uni.$emit('change', params)触发在页面或者其他地方监听的uni.on('change', (params) => {})接收。

需要注意的是,我们的页面的onLoad周期中进行监听,最好就在onUnload周期中取消监听。

uni.$emit('payModalEvent', {
    type: 'account'
})

自定义支付弹窗

自定义支付弹窗的一个实战逻辑:

1、在methods中实现queryLockModalText函数返回弹窗文案

2、阅读器页面在锁定章节弹出解锁组件

3、用户点击解锁组件按钮,触发onClickUnlockPrimaryButton|onClickUnlockSecondaryButton事件监听,根据我们实现queryLockModalText函数的逻辑判断展示广告还是支付弹窗

4、如果是支付弹窗,我们就可以获取onClickUnlockPrimaryButton(res)|onClickUnlockSecondaryButton(res)事件回调的res参数中的showPayModalclosePayModal函数

5、使用$novel.setClosePayModal(closePayModal)closePayModal函数存到全局,等待我们之后在其他地方调用它关闭支付弹窗。

6、使用showPayModal(params)函数传递参数,唤起支付弹窗组件后可以在组件内接收。

7、在支付弹窗组件内,用户触发关闭按钮事件时,使用$novel.callClosePayModal()关闭组件。用户触发购买或付费完成事件时,使用uni.emit('payModalEvent', params)通信父页面(阅读器),在uni.on('payModalEvent', this.handlePayModalEvent)监听事件中接收参数进行业务处理,最后再使用$novel.callClosePayModal()关闭弹窗组件。

示例代码:

在公用js文件中定义closePayModal的存取函数

// novel.js
let closePayModal;
function setClosePayModal(fn) {
    closePayModal = fn
}

function callClosePayModal() {
    if(typeof closePayModal === 'function') {
        closePayModal()
    }
}

在阅读器页面中使用

// pages/novel-plugin/index
novelManage.onClickUnlockPrimaryButton((res) => {
    console.log('解锁按钮1触发', res)
    novelManage.onClickUnlockPrimaryButton((res) => {
        console.log('解锁按钮1触发', res)
        const {
            showPayModal,
            closePayModal
        } = res
        showPayModal({
            test: '解锁按钮1触发',
            bookInfo
        })
        $novel.setClosePayModal(closePayModal)
    })
})

// 充值等事件完成触发关闭支付弹窗
$novel.callClosePayModal()

在支付弹窗组件中触发关闭事件时使用

$novel.callClosePayModal()

常见问题

1、未解锁章节免费字数

免费字数是由服务端调用小说资源接入接口时更新控制的,小程序内部无法通过传入参数来设置。

2、小说等级的作用

小说资源接入时grade等级枚举值只有A|BA级需要把小说所有章节内容都接入(至少1章)才能上架,强制只有A级接入的小说才可以通过巨量引擎进行投放。

3、阅读器内目录信息的展示

阅读器内目录信息的展示(包含且不限于章节数、章节标题、字数)都是由服务端接入小说资源库创建更新的。小程序内调用queryContentsInfo查询更新章节解锁信息时,只有章节IDchapterId和解锁状态status字段是有意义的。

4、完成内容库接入

完成内容库的接口对接,且小程序内上传/被授权的小说内容的dau占比该小程序总dau的80%以上,视为该小程序“完成内容库接入”。

接入完成的标准是根据改造后的阅读器path为pages/novel_plugin/index,统计小程序的dau占比该小程序总dau的80%以上,视为该小程序”完成内容库接入”。也就是统计投放入口路径,pages/novel_plugin/index路径/投放入口路径总数大于80%代表接入完成。

写在最后

相关文章: uni-app(Vue2)编译为抖音小程序时,自动生成package.json文件配置行业SDK权限

参考文档: 抖音小程序小说阅读器开发者文档

参考文档: 抖音小程序小说资源接入文档V2

uniapp Vue2 小程序 前端 小说阅读器