# 小程序插件

功能背景

开发者可在小程序代码中声明引入的插件代码,使用开发者工具进行编译时,开发者工具会从服务端获取插件代码一起打包编译。插件功能需要在基础库版本≥2.11.1SDK版本≥2.34.0的环境下才可使用。

# 1. 功能介绍

小程序插件是对一组 js 接口,自定义组件或页面的封装,相对于普通 js 文件或自定义组件,插件拥有更强的独立性。但插件不可以直接调用,必须要嵌入小程序中才能被用户使用,不能独立运行。当第三方小程序在使用插件时,也无法看到插件的代码,因此插件适合用来封装自己的功能或服务,并通过插件的形式提供给第三方小程序进行展示与使用。

开发者可以像开发小程序一样,编写并上传插件,在插件发布之后 “人民网+”小程序 会托管插件代码,其他小程序调用时,上传的插件代码会随小程序一起下载运行。

在 “人民网+”小程序 体系内,我们会基于沙箱技术对小程序和小程序使用的每个插件进行数据安全保护,保证它们之间不能窃取其他任何一方的数据(除非数据被主动传递给另一方)。插件中所需要请求的域名需要同样在「域名设置」页面中进行配置,插件中也能够调用所有 “人民网+”小程序 的 API 功能。

# 2. 操作说明

对于小程序的插件使用,分别分为开发者开发插件,与小程序开发者使用插件两部分。

# 2.1 开发插件

# 2.1.1 创建插件项目

开发者需要在开发者工具中选择创建小程序,并在项目类型中选择「小程序插件」。

当完成插件小程序的创建后,我们可以在项目目录中发现有两个目录与一个文件:

  • plugin目录:插件代码的目录;
  • miniprogram目录:放置 “人民网+”小程序,用于调试插件;
  • fide.project.config.json文件:“人民网+”小程序 项目配置文件;

在 miniprogram 目录中的内容可以当成普通小程序进行编写,用来对插件进行预览、调试与审核。

# 2.1.2 插件目录结构

如果您通过开发者工具创建了插件类型的小程序,您也可以直接通过查看其中的内容进一步了解小程序插件。

一个插件可以包括若干个自定义组件、页面和一组 js 接口。插件的目录内容如下:

plugin
├── components
│   ├── hello-component.js   // 插件提供的自定义组件(可以有多个)
│   ├── hello-component.json
│   ├── hello-component.fxml
│   └── hello-component.ftss
├── pages
│   ├── hello-page.js        // 插件提供的页面(可以有多个,自基础库版本 2.1.1 支持)
│   ├── hello-page.json
│   ├── hello-page.fxml
│   └── hello-page.ftss
├── index.js                 // 插件的 js 接口
└── plugin.json              // 插件配置文件

# 2.1.3 插件配置文件

向使用者小程序开放的所有自定义组件、页面和 js 接口都必须在插件配置文件 plugin.json列出,格式如下:

{
  "publicComponents": {
    "hello-component": "components/hello-component"
  },
  "pages": {
    "hello-page": "pages/hello-page"
  },
  "main": "index.js"
}

这个配置文件将向使用者小程序开放一个自定义组件hello-component,一个页面hello-pageindex.js下导出的所有 js 接口。

# 2.1.4 开发自定义组件

插件可以定义若干个自定义组件,这些自定义组件都可以在插件内相互引用。但提供给使用者小程序使用的自定义组件必须在配置文件的publicComponents段中列出(参考上文)。

除去接口限制以外,自定义组件的编写和组织方式与一般的自定义组件相同,每个自定义组件由fxmlftssjsjson四个文件组成。具体可以参考 自定义组件的文档

json文件中需要引入自定义组件时,使用plugin://协议指明插件的引用名和自定义组件名即可,如:

{
  "usingComponents": {
    "hello-component": "plugin://myPlugin/hello-component"
  }
}

# 2.1.5 开发页面

插件从小程序基础库版本 2.1.1 开始支持页面。插件可以定义若干个插件页面,可以从本插件的自定义组件、其他页面中跳转,或从使用者小程序中跳转。所有页面必须在配置文件的pages段中列出(参考上文)。

除去接口限制以外,插件的页面编写和组织方式与一般的页面相同,每个页面由fxmlftssjsjson四个文件组成。

插件执行页面跳转的时候,可以使用 navigator组件。当插件跳转到自身页面时, url应通过plugin://前缀设置为这样的形式:url="plugin://hello-plugin/hello-page",如:

<navigator id="nav" url="plugin://hello-plugin/hello-page">
  Go to Plugin page
</navigator>

同样,在插件自身的页面中,插件还可以调用 ft.navigateTo来进行页面跳转,url格式与使用 navigator组件时相仿。

# 2.1.6 开发接口

插件可以在接口文件(在配置文件中指定,详情见上文)中 export 一些 js 接口,供插件的使用者调用,如:

// plugin/pages/hello-page.js
Page({
  data: {},
  onLoad() {
    console.log('This is a plugin page!')
  }
})

# 2.1.7 预览插件

插件可以像小程序一样预览和上传,但插件没有体验版。插件会同时有多个线上版本,由使用插件的小程序决定具体使用的版本号。手机预览和提审插件时,会使用一个特殊的小程序来套用项目中 miniprogram 文件夹下的小程序,从而预览插件。

# 2.2 上传、发布插件

请注意

在 “人民网+”小程序 中,插件分为平台发布插件与自有插件,所有用户都可以看到平台发布的插件,普通开发者上传的插件则只可在自有团队或企业中可见。

开发者如果需要上传自有插件,需要进入「小程序管理-小程序插件」页面,点击其中的「新增插件」按钮,并在其中分别输入插件名称,头像与介绍信息以完成插件新建。

完成插件新建后,点击右侧的「更新插件」,在其中分别录入插件的版本号,更新日志,并上传代码包,就可以完成插件的上传了。

# 2.3 使用插件

# 2.3.1 寻找需要的插件

在使用插件前,开发者可登录「小程序开放平台-小程序管理-小程序插件」,获取插件ID信息。在 “人民网+”小程序 中使用插件不需要申请,可以直接使用。

使用插件时,插件的代码对于使用者来说是不可见的。为了正确使用插件,使用者应查看插件详情页面中的“开发文档”一节,阅读由插件开发者提供的插件开发文档,通过文档来明确插件提供的自定义组件、页面名称及提供的 js 接口规范等。

# 2.3.2 引入插件代码包

使用插件前,使用者要在app.json中声明需要使用的插件,例如:

{
  "plugins": {
    "myPlugin": {
      "version": "1.0.0",
      "provider": "插件 id"
    }
  }
}

如上例所示,plugins定义段中可以包含多个插件声明,每个插件声明以一个使用者自定义的插件引用名作为标识,并指明插件的 ID和需要使用的版本号。

其中,引用名(如上例中的myPlugin)由使用者自定义,无需和插件开发者保持一致或与开发者协调。在后续的插件使用中,该引用名将被用于表示该插件。

# 2.3.3 在分包内引入插件代码包

如果插件只在一个分包内用到,可以将插件仅放在这个分包内,例如:

{
  "subpackages": [
    {
      "root": "packageA",
      "pages": [
        "pages/cat",
        "pages/dog"
      ],
      "plugins": {
        "myPlugin": {
          "version": "1.0.0",
          "provider": "插件 id"
        }
      }
    }
  ]
}

在分包内使用插件有如下限制:

  • 仅能在这个分包内使用该插件;
  • 同一个插件不能被多个分包同时引用;

# 2.3.4 使用自定义组件

使用插件提供的自定义组件,和 使用普通自定义组件 的方式相仿。在json文件定义需要引入的自定义组件时,使用plugin:// 协议指明插件的引用名和自定义组件名,例如:

{
  "usingComponents": {
    "hello-component": "plugin://myPlugin/hello-component"
  }
}

出于对插件的保护,插件提供的自定义组件在使用上有一定的限制:

  • 默认情况下,页面中的 this.selectComponent接口无法获得插件的自定义组件实例对象;
  • ft.createSelectorQuery等接口的>>> 选择器无法选入插件内部。

# 2.3.5 使用页面

需要跳转到插件页面时,url 使用plugin://前缀,形如plugin://PLUGIN_NAME/PLUGIN_PAGE, 如:

<navigator url="plugin://myPlugin/hello-page">
  Go to pages/hello-page!
</navigator>

# 2.3.6 使用接口

使用插件的js接口时,可以使用requirePlugin方法。例如,插件提供一个名为hello的方法和一个名为world的变量,则可以像下面这样调用:

var myPluginInterface = requirePlugin('myPlugin');

myPluginInterface.hello();
var myWorld = myPluginInterface.world;

接口也可以通过插件的 id 来获取接口,如:

var myPluginInterface = requirePlugin('插件 id');

# 2.3.7 导出到插件

使用插件的小程序可以导出一些内容,供插件获取。具体来说,在声明使用插件时,可以通过export字段来指定一个文件,如:

{
  "myPlugin": {
    "version": "1.0.0",
    "provider": "插件 id",
    "export": "index.js"
  }
}

则该文件(上面的例子里是index.js)导出的内容可以被这个插件用全局函数获得。在上面的文件中,使用插件的小程序做了如下导出:

// index.js
module.exports = { whoami: 'MiniProgram' }

那么插件就可以获得上面导出的内容:

// plugin
requireMiniProgram().whoami // 'MiniProgram'

具体导出什么内容,可以阅读插件开发文档,和插件的开发者做好约定。

当插件在分包中时,这个特性也可以使用,但指定的文件的路径是相对于分包的。例如在root: packageA 的分包中指定了export: exports/plugin.js,那么被指定的文件在文件系统上应该是/packageA/exports/plugin.js

请注意

使用的多个插件的导出互不影响,两个插件可以导出同一个文件,也可以是不同的文件。但导出同一个文件时,如果一个插件对导出内容做了修改,那么另一个插件也会被影响。

# 2.3.8 为插件提供自定义组件

有时,插件可能会在页面或者自定义组件中,将一部分区域交给使用的小程序来渲染,因此需要使用的小程序提供一个自定义组件。但由于插件中不能直接指定小程序的自定义组件路径,因此需要通过为插件指定抽象节点(generics)的方式来提供。

如果是插件的自定义组件需要指定抽象节点实现,可以在引用时指定:

<!-- miniprogram/page/index.fxml -->
<plugin-view generic:mp-view="comp-from-miniprogram" />

可以通过配置项为插件页面指定抽象组件实现。例如,要给插件名为plugin-index的页面中的抽象节点mp-view指定小程序的自定义组件components/comp-from-miniprogram作为实现的话:

{
  "myPlugin": {
    "provider": "插件 id",
    "version": "1.0.0",
    "genericsImplementation": {
      "plugin-index": {
        "mp-view": "components/comp-from-miniprogram"
      }
    }
  }
}

# 3. “人民网+”小程序 提供的插件

# 3.1 单向视频录制插件

# 3.1.1 快速开始

// pages/video/video.js
Page({

  data: {
    recordTime: 30000,
    top: 20,
    stepList: [
      {
        audioSrc: 'https://devtest-1252553964.cos.ap-guangzhou.myqcloud.com/1.mp3',
        showTime: 0,
        textList: [{
          text: '第一行文本',
          margin: '0 0 6rpx 0'
        }, {
          text: [{
            text: '第二行'
          }, {
            text: '高亮高亮',
            color: '#ee6f2d',
            fontWeight: 'bold',
            margin: '0 6rpx'
          }, {
            text: '文本文本文本文本文本'
          }]
        }, {
          text: '第三行文本文本文本文本文本',
          color: '#ee6f2d',
          margin:  '6rpx'
        }]
      },
      {
        audioSrc: 'https://devtest-1252553964.cos.ap-guangzhou.myqcloud.com/2.mp3',
        showTime: 8000,
        textList: [{
          text: '第二个文本',
          margin: '0 0 6rpx 0'
        }, {
          text: [{
            text: '文本'
          }, {
            text: '高亮',
            color: '#ee6f2d',
            fontWeight: 'bold',
            margin: '0 6rpx'
          }, {
            text: '文本文本文本文本文本'
          }, {
            text: '高亮',
            color: '#ee6f2d',
            fontWeight: 'bold',
            margin: '0 6rpx'
          }]
        }]
      },
      {
        audioSrc: 'https://devtest-1252553964.cos.ap-guangzhou.myqcloud.com/3.mp3',
        showTime: 18000,
        textList: [{
          text: '3第三个文本',
          margin: '0 0 6rpx 0'
        }, {
          text: [{
            text: '3第二行'
          }, {
            text: '3高亮3',
            color: '#ee6f2d',
            fontWeight: 'bold',
            margin: '0 6rpx'
          }, {
            text: '333文本文本文本文本文本文本333'
          }]
        }]
      }
    ],
    buttonStyle: {
      width: '16vw',
      height: '16vw',
      bottom: '80rpx'
    }
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    wx.showLoading()
  },

  onRecordReady() {
    wx.hideLoading()
  },

  onRecordStart() {
    wx.showToast({
      title: '录制开始',
      icon: 'none'
    })
  },

  onRecordEnd(res) {
    const { tempVideoPath } = res.detail
    wx.showToast({
      title: '录制结束,地址:' + tempVideoPath,
      icon: 'none'
    })
    setTimeout(() => {
      wx.navigateTo({
        url: '/pages/index/index?url=' + encodeURIComponent(tempVideoPath)
      })
    }, 3500)
  },

  onRecordError(res) {
    const { errMsg } = res.detail
    wx.showToast({
      title: errMsg,
      icon: 'none'
    })
  }
})
// pages/video/video.json
"usingComponents": {
    "video-recognition": "plugin://video/video-recognition"
}
// pages/video/video.fxml
<view style="width: 100vw;height: 100vh;">
  <video-recognition recordTime="{{recordTime}}"
                     top="{{top}}"
                     stepList="{{stepList}}"
                     buttonStyle="{{buttonStyle}}"
                     mask="/assets/img_mask_person@3x.png"
                     resolution="low"
                     bind:onRecordReady="onRecordReady"
                     bind:onRecordStart="onRecordStart"
                     bind:onRecordEnd="onRecordEnd"
                     bind:onRecordError="onRecordError">

  </video-recognition>
</view>

# 3.1.2 参数

属性 类型 是否必传 默认值 说明
resolution String medium 分辨率,可选值:low、medium、high 只在初始化时有效,不能动态变更
mask String - 取景区域的遮罩资源路径,建议使用小程序内资源的相对路径,https 地址会有加载耗时, 遮罩会按 width 100% height 100% 的尺寸放在 camera 上,注意和组件尺寸相匹配
recordTime Number 30000 录制时间,单位为毫秒
top Number 20 单位 rpx 文本提示距顶部的距离, 也可修改 video-recognition 组件内的 wxss,自定义文本的 position
stepList Array[Object] - 见下方
buttonStyle Object - 控制录制按钮的样式,可对按钮进行位置上的微调目前仅支持以下字段:width、height、left、top、bottom、right 只在初始化时有效,不能动态变更
onRecordReady EventHandler - 通过 onRecordReady 绑定 ready 前会进行一些异步资源的下载,资源准备好后触发可用于使用组件的 page 页面判断是否准备完毕,从而控制 loading 展示
onRecordStart EventHandler - 通过 bind:onRecordStart 绑定录制开始时触发
onRecordEnd EventHandler - 通过 bind:onRecordEnd 绑定录制结束时触发回调方法参数 res:tempVideoPath 录制视频的本地文件地址
onRecordError EventHandler - 通过 bind:onRecordError 绑定录制报错时触发回调方法参数 res:errMsg 发生错误时的报错信息

setpList 每步的语和提示案配置,最大支持长度为 3,数据元素结构如下:

{
    "audioSrc": "https://xxxxx.mp3",
    "showTime": 0,
    "textList": []
}
  • audioSrc - 音频链接,建议使用 https 链接,组件 attached 会下载音频资源,若下载失败会执行 error 回调,报资源加载错误(注意:mp3 的域名需在管理后台添加到白名单内,否则会下载失败);
  • showTime - 文本提示和音频的展示时间,毫秒数,0 表示初始展示,2000 表示录制开始 2s 时展示;
  • textList - 文本提示,数组类型,参数对象如下;
{
    "text": '请用普通话大声朗读'
}
  • text - 文本内容 可添加 width|height|padding|margin|color|fontSize|fontWeight|textAlign 等样式属性简单控制文本样式:
{
    "text": "文本",
    "color": "red",
    "fontWeight": "bold",
    "margin": "0 20rpx"
}

注意,一个textList子元素,展示时会以单行不换行展示,可按需拆分成多行多个子元素; 另外,如果单行内容内需要个别词语高亮展示,text属性可以传入数组,属性与上述对象参数一致,如下:

{
    "text": [{
        "text": "文本"
    }, {
        "text": "高亮文本",
        "color": "red",
        "fontWeight": "bold",
        "margin": "0 20rpx"
    }, {
        "text": "文本"
    }]
}

只在初始化时有效,不能动态变更.

# 3.2 虚拟键盘

# 3.2.1 快速开始

// component.json
"usingComponents": {
    "keyboard": "plugin://virtual-keyboard/keyboard"
}
// wxml
<keyboard show="{{ show }}" closeButtonText="完成" bindinput="changeInput" binddelete="changeDetete" />

# 3.2.2 参数

属性 类型 默认值 说明
title string 键盘上方显示的标题
show boolean false 控制键盘是否显示
theme string default default/custom 当为 custom 时右侧增加删除和完成按钮
themeColor string #2e7cff 设置主题色
zIndex number/string 999 层级
isRandomLetter boolean false 乱序字符
isRandomNumber boolean false 乱序数字
normalType string number number/letter/symbol 控制显示的键盘类型
closeButtonText string 关闭按钮的文字
extraKey [string] 额外的按键
icon string 标题栏 icon 的路径