更新记录
1.0.0(2026-09-08)
- 首次发布
- API 型 UTS 插件(vue / nvue 页面通用),基于 TBS 文档 SDK 公网版,Dialog 弹窗预览方式
- 提供 initEngine / openDocumentByDialog / closeDocument / isEngineLoaded / canOpenFileExt / setLicenseKey 六个 API
- 支持 doc、docx、rtf、ppt、pptx、xls、xlsx、xlsm、csv、pdf、txt、epub、chm、ofd 格式本地文档
- 注意:与组件插件 zy-tbsfile 含同一 TBS AAR,不可同时打进同一自定义基座
平台兼容性
uni-app(4.72)
| Vue2 |
Vue2插件版本 |
Vue3 |
Vue3插件版本 |
Chrome |
Safari |
app-vue |
app-vue插件版本 |
app-nvue |
app-nvue插件版本 |
Android |
Android插件版本 |
iOS |
鸿蒙 |
| √ |
1.0.0 |
√ |
1.0.0 |
× |
× |
√ |
1.0.0 |
√ |
1.0.0 |
8.0 |
1.0.0 |
× |
× |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| × |
× |
× |
× |
× |
× |
× |
× |
× |
× |
× |
× |
uni-app x(4.72)
| Chrome |
Safari |
Android |
iOS |
鸿蒙 |
微信小程序 |
| × |
× |
× |
× |
× |
× |
zy-tbsfile-api 腾讯TBS文档预览API插件
基于腾讯浏览服务(TBS)文档 SDK 公网版封装的 API 型 UTS 插件(Dialog 弹窗预览方式)。
与组件插件 zy-tbsfile(component-uts,仅 nvue 页面可用)不同,本插件通过 import 调用,vue 页面与 nvue 页面均可使用。打开文档后由 SDK 弹出默认全屏 Dialog(带默认标题栏),单击翻页、滑动、缩放、页码显示均由 SDK 默认 UI 处理。
SDK 接口文档:https://cloud.tencent.com/document/product/1645/83900
支持格式
doc、docx、rtf、ppt、pptx、xls、xlsx、xlsm、csv、pdf、txt、epub、chm、ofd
仅支持 Android 平台(TBS 文档 SDK 官方仅提供 Android SDK)。
使用前提
- 需配合腾讯云 TBS 文档 SDK LicenseKey 使用,购买方式参见 License 使用说明。
- 请务必确保用户同意隐私政策后再调用
initEngine。
- 真机运行需使用自定义基座(三方 SDK 不进标准基座)。
- 与 zy-tbsfile 组件插件互斥:两个插件包含同一个 TBS AAR,不可同时打进同一个自定义基座(会导致 Duplicate class 打包失败),请按需选用其一。
快速上手
import { initEngine, openDocumentByDialog, closeDocument, isEngineLoaded } from '@/uni_modules/zy-tbsfile-api'
// 1. 初始化引擎(App 启动后仅需调用一次,需在用户同意隐私政策后)
initEngine({
license: 'your licenseKey',
success: (res) => {
console.log('引擎初始化成功')
},
fail: (res) => {
console.log('初始化失败: ' + res.code + ' ' + res.message)
}
})
// 2. 打开网络文档:TBS 仅支持本地路径,先下载
uni.downloadFile({
url: 'https://xxx.cos.ap-beijing.myqcloud.com/test.pdf',
success: (res) => {
if (res.statusCode === 200) {
const localPath = plus.io.convertLocalFileSystemURL(res.tempFilePath)
openDocumentByDialog({
filePath: localPath, // 本地绝对路径
// fileExt: 'pdf', // 可不传,自动从路径提取
// enableLongPressMenu: true, // 长按复制(PDF/DOCX/XLSX/TXT)
// gotoLastPos: true, // 跳转上次浏览位置
success: (res) => {
console.log('文档已打开: ' + res.typeDes)
},
fail: (res) => {
console.log('打开失败: ' + res.code + ' ' + res.message)
},
onClose: () => {
console.log('文档已关闭(用户关闭弹窗时也会触发)')
}
})
}
}
})
// 3. 打开本地文档(如沙盒目录文件)
openDocumentByDialog({ filePath: '/storage/emulated/0/Android/data/<包名>/files/tbsfile/test.txt' })
// 4. 主动关闭(用户关闭弹窗时 SDK 自动释放,通常无需调用)
closeDocument({ success: () => {} })
API 说明
| 方法 |
参数 |
返回 |
说明 |
| setLicenseKey |
license: string |
void |
设置 LicenseKey(initEngine 的 license 非空时会自动调用,一般无需单独调用) |
| initEngine |
options: InitEngineOptions |
void |
异步初始化引擎,success/fail 回调 |
| isEngineLoaded |
无 |
boolean |
查询引擎是否初始化成功 |
| canOpenFileExt |
ext: string |
boolean |
判断格式是否支持 |
| openDocumentByDialog |
options: OpenDocumentOptions |
void |
Dialog 弹窗方式打开文档 |
| closeDocument |
options: CloseDocumentOptions |
void |
关闭文档并释放资源 |
InitEngineOptions
| 属性 |
类型 |
必填 |
说明 |
| license |
string |
是 |
LicenseKey |
| success |
(res: TbsInitResult) => void |
否 |
初始化成功 |
| fail |
(res: TbsFailResult) => void |
否 |
初始化失败(res.code / res.message) |
OpenDocumentOptions
| 属性 |
类型 |
必填 |
说明 |
| filePath |
string |
是 |
本地文件绝对路径(网络文档会报 9020004,需先下载) |
| fileExt |
string |
否 |
文件后缀名,不传时自动从路径提取 |
| tempPath |
string |
否 |
临时目录路径,默认沙盒 getExternalFilesDir("tbsfile_temp") |
| enableLongPressMenu |
boolean |
否 |
长按菜单复制(PDF/DOCX/XLSX/TXT),默认 false |
| pptPageMode |
boolean |
否 |
PPT 翻页模式(默认滑动模式),默认 false |
| gotoLastPos |
boolean |
否 |
自动跳转上次浏览位置,默认 false |
| contentBgColor |
string |
否 |
文档视图背景颜色,如 "#ffffff" |
| topBarBgColor |
string |
否 |
顶栏颜色 |
| topBarHeight |
number |
否 |
顶栏高度(px) |
| success |
(res: TbsOpenResult) => void |
否 |
文档打开成功(res.typeId / res.typeDes) |
| fail |
(res: TbsFailResult) => void |
否 |
打开失败(res.code / res.message) |
| onClose |
() => void |
否 |
文档关闭(用户关闭弹窗时也会触发) |
错误码
| code |
说明 |
| 9020001 |
引擎未初始化,请先调用 initEngine |
| 9020002 |
引擎初始化失败 |
| 9020003 |
TBS 不支持的文件格式 |
| 9020004 |
文件不存在(注意:传网络地址也会报此码,请先下载到本地) |
| 9020005 |
参数错误 |
| 9020006 |
打开文档失败 |
| 9020007 |
获取 Activity 失败 |
示例代码
<template>
<view class="page">
<!-- 引擎初始化 -->
<view class="section">
<view class="section-title">
<text>引擎初始化</text>
<text class="badge" :class="engineReady ? 'badge-ok' : 'badge-wait'">{{ engineReady ? '已初始化' : '未初始化' }}</text>
</view>
<view class="form-item">
<text class="form-label">LicenseKey</text>
<input class="text-input" v-model="license" placeholder="请输入 TBS LicenseKey" />
</view>
<view class="btn-row">
<button class="btn btn-primary" @click="handleInitEngine">初始化引擎</button>
<button class="btn btn-plain" @click="handleCheckEngine">查询状态</button>
</view>
</view>
<!-- 打开文档 -->
<view class="section">
<view class="section-title"><text>打开文档(Dialog 弹窗方式)</text></view>
<view class="form-item">
<text class="form-label">网络文档URL</text>
<input class="text-input" v-model="netUrl" placeholder="https 链接,需带扩展名" />
</view>
<view class="btn-row">
<button class="btn btn-plain" @click="handleDownload">下载到本地</button>
</view>
<view class="form-item">
<text class="form-label">本地绝对路径</text>
<input class="text-input" v-model="filePath" placeholder="/storage/.../test.pdf" />
</view>
<view class="opt-row">
<text class="opt-label">长按复制</text>
<switch :checked="enableLongPressMenu" @change="(e) => enableLongPressMenu = e.detail.value" color="#4f6ef7" />
</view>
<view class="opt-row">
<text class="opt-label">跳转上次位置</text>
<switch :checked="gotoLastPos" @change="(e) => gotoLastPos = e.detail.value" color="#4f6ef7" />
</view>
<view class="btn-row">
<button class="btn btn-primary" @click="handleOpen">打开文档</button>
<button class="btn btn-warn" @click="handleClose">关闭文档</button>
</view>
<view class="tip">提示:TBS 仅支持本地路径,网络文档请先下载;用户关闭弹窗后 onClose 会触发</view>
</view>
<!-- 日志 -->
<view class="section log-section">
<view class="section-title">
<text>日志</text>
<text class="clear-btn" @click="logs = []">清空</text>
</view>
<scroll-view class="log-list" scroll-y>
<view class="log-item" v-for="(item, index) in logs" :key="index">
<text>{{ item }}</text>
</view>
<view v-if="logs.length === 0" class="log-empty"><text>暂无日志</text></view>
</scroll-view>
</view>
</view>
</template>
<script>
import {
initEngine,
openDocumentByDialog,
closeDocument,
isEngineLoaded,
canOpenFileExt
} from '@/uni_modules/zy-tbsfile-api'
export default {
data() {
return {
license: 'PooBEXT8UKSBQUN57PcdywzP+6OO3cRQ3qASkQffYtOSaM5hAjR3eLKfp4ck4wnT',
netUrl: 'https://hg-zhjd-test-1307093093.cos.ap-beijing.myqcloud.com/centralBank/%E3%80%90Python%E5%BC%80%E5%8F%91%E5%B7%A5%E7%A8%8B%E5%B8%88_%E9%83%91%E5%B7%9E%208-10K%E3%80%91%E5%BE%90%E5%A8%81%2010%E5%B9%B4.pdf',
filePath: '',
enableLongPressMenu: false,
gotoLastPos: false,
engineReady: false,
logs: []
}
},
onUnload() {
// 页面卸载时关闭文档并释放资源
if (this.engineReady) {
closeDocument({})
}
},
methods: {
addLog(msg) {
const now = new Date()
const pad = (n) => (n < 10 ? '0' + n : '' + n)
const time = pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds())
this.logs.unshift('[' + time + '] ' + msg)
if (this.logs.length > 100) {
this.logs.pop()
}
},
handleInitEngine() {
if (!this.license) {
uni.showToast({ title: '请输入 LicenseKey', icon: 'none' })
return
}
this.addLog('开始初始化引擎...')
initEngine({
license: this.license,
success: (res) => {
this.engineReady = true
this.addLog('引擎初始化成功: ' + res.message)
uni.showToast({ title: '初始化成功', icon: 'success' })
},
fail: (res) => {
this.engineReady = false
this.addLog('初始化失败: code=' + res.code + ' ' + res.message)
uni.showToast({ title: '初始化失败', icon: 'none' })
}
})
},
handleCheckEngine() {
const loaded = isEngineLoaded()
this.engineReady = loaded
this.addLog('isEngineLoaded: ' + loaded)
},
// 下载网络文档到沙盒临时目录,成功后自动填充本地路径
// TBS 仅支持本地路径;tempFilePath 保留扩展名,便于插件提取 fileExt
handleDownload() {
if (!this.netUrl) {
uni.showToast({ title: '请输入网络文档URL', icon: 'none' })
return
}
this.addLog('开始下载...')
uni.showLoading({ title: '下载中...', mask: true })
uni.downloadFile({
url: this.netUrl,
success: (res) => {
uni.hideLoading()
if (res.statusCode === 200) {
const abs = plus.io.convertLocalFileSystemURL(res.tempFilePath)
this.filePath = abs
this.addLog('下载完成: ' + abs)
} else {
this.addLog('下载失败: HTTP ' + res.statusCode)
}
},
fail: (err) => {
uni.hideLoading()
this.addLog('下载失败: ' + JSON.stringify(err))
}
})
},
handleOpen() {
if (!this.engineReady) {
uni.showToast({ title: '请先初始化引擎', icon: 'none' })
return
}
if (!this.filePath) {
uni.showToast({ title: '请先填写文件路径', icon: 'none' })
return
}
// 从路径提取后缀并检查格式支持
const idx = this.filePath.lastIndexOf('.')
const ext = idx >= 0 ? this.filePath.substring(idx + 1).toLowerCase() : ''
if (ext && !canOpenFileExt(ext)) {
this.addLog('不支持格式: ' + ext)
uni.showToast({ title: '不支持 ' + ext + ' 格式', icon: 'none' })
return
}
this.addLog('打开文档: ' + this.filePath)
openDocumentByDialog({
filePath: this.filePath,
enableLongPressMenu: this.enableLongPressMenu,
gotoLastPos: this.gotoLastPos,
success: (res) => {
this.addLog('文档已打开: typeId=' + res.typeId + ' ' + res.typeDes)
},
fail: (res) => {
this.addLog('打开失败: code=' + res.code + ' ' + res.message)
uni.showToast({ title: '打开失败: ' + res.code, icon: 'none' })
},
onClose: () => {
this.addLog('文档已关闭 (onClose)')
}
})
},
handleClose() {
closeDocument({
success: (res) => {
this.addLog('closeDocument: ' + res.message)
}
})
}
}
}
</script>
<style>
.page {
min-height: 100vh;
background-color: #f5f6fa;
padding: 24rpx;
}
.section {
background-color: #ffffff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 24rpx;
}
.section-title {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
font-size: 30rpx;
font-weight: bold;
color: #333333;
margin-bottom: 24rpx;
}
.badge {
font-size: 22rpx;
font-weight: normal;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
.badge-ok {
background-color: #e6f7e9;
color: #34a853;
}
.badge-wait {
background-color: #fff4e5;
color: #f57c00;
}
.form-item {
margin-bottom: 20rpx;
}
.form-label {
font-size: 26rpx;
color: #666666;
margin-bottom: 10rpx;
}
.text-input {
height: 72rpx;
background-color: #f5f6fa;
border-radius: 10rpx;
padding: 0 20rpx;
font-size: 26rpx;
}
.btn-row {
display: flex;
flex-direction: row;
margin-top: 10rpx;
}
.btn {
flex: 1;
font-size: 28rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 10rpx;
margin: 0 10rpx;
}
.btn-primary {
background-color: #4f6ef7;
color: #ffffff;
}
.btn-plain {
background-color: #eef1ff;
color: #4f6ef7;
}
.btn-warn {
background-color: #fff1f0;
color: #e54545;
}
.opt-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 12rpx 0;
}
.opt-label {
font-size: 26rpx;
color: #333333;
}
.tip {
font-size: 22rpx;
color: #999999;
margin-top: 16rpx;
line-height: 1.6;
}
.log-section {
/* 占满剩余高度 */
flex: 1;
}
.log-list {
height: 400rpx;
}
.log-item {
font-size: 22rpx;
color: #555555;
padding: 8rpx 0;
border-bottom: 1rpx solid #f0f0f0;
word-break: break-all;
}
.log-empty {
font-size: 24rpx;
color: #bbbbbb;
text-align: center;
padding: 40rpx 0;
}
.clear-btn {
font-size: 24rpx;
color: #4f6ef7;
font-weight: normal;
}
</style>
注意事项
- TBS 引擎同一时刻仅支持一个文档,重复调用 openDocumentByDialog 会先自动关闭已打开文档。
- 单击/滑动/缩放、页码显示等由 SDK 默认弹窗 UI 自行处理,插件不透传手势事件(如需内嵌页面布局和页码跳转,请使用组件插件 zy-tbsfile 的 Layout 方式,但两者不可同时打包)。
- 混淆配置(离线打包场景):
-dontwarn com.tencent.tbs.reader.**
-keep class com.tencent.tbs.reader.** { *; }
- 隐私合规:SDK 初始化会采集设备信息和应用信息,请确保用户同意隐私政策后再调用 initEngine。