鸿蒙ArkTs如何实现pdf预览功能?
鸿蒙ArkTs如何实现pdf预览功能?
- 前言
- PDFKit运行示例代码报错
- 真机运行
- 先看效果
- 一、预览本地pdf文件
- 二、预览线上的pdf文件
- 三、预览沙箱目录中pdf的文件(重点)
- 效果中的整体代码
- 总结
- Harmony OS NEXT版本(接口及解决方案兼容API12版本或以上版本)
前言
在开发鸿蒙App时,你是否做过pdf预览功能。是否也和我一样碰壁了,来看看我遇到的问题,以及我是如何实现的吧。
PDFKit运行示例代码报错
the requested module '@hms:officeservice.PdfView' does not provide an export name 'pdfViewManager' which imported by 'xxxx'
真机运行
本来以为用真机就能运行了,没想到还是报错

那么下面来看看我是如何实现的吧
先看效果

视频转完gif,视觉上看起来有点卡,实际运行不卡。
一、预览本地pdf文件
预览本地的pdf文件很简单,使用Web组件加载即可。
pdf文件目录:harmonyApp\entry\src\main\resources\rawfile\test.pdf
具体代码如下:
import web_webview from '@ohos.web.webview';@Entry
@Component
struct Index {webviewController: web_webview.WebviewController = new web_webview.WebviewController();build() {Column() {// src-本地pdf文件Web({ src: $rawfile('test.pdf'), controller: this.webviewController }).layoutWeight(1).domStorageAccess(true)}.height('100%')}
}
二、预览线上的pdf文件
这里的线上的pdf文件是指可以在浏览器直接打开预览的pdf文件,还有一种是在浏览器打开是直接进入下载的,那么就需要我们进一步处理了,第三点有详解。
这样的文件预览也很简单,使用Web组件加载即可。
具体代码如下:
import web_webview from '@ohos.web.webview';@Entry
@Component
struct Index {webviewController: web_webview.WebviewController = new web_webview.WebviewController();build() {Column() {// 线上pdf链接Web({ src: 'http://www.cztouch.com/upfiles/soft/testpdf.pdf', controller: this.webviewController }).layoutWeight(1).domStorageAccess(true)}.height('100%')}
}
三、预览沙箱目录中pdf的文件(重点)
这种就比较麻烦了,有的pdf链接在浏览器打开直接跳转下载不会预览,那么就需要我们下载到沙箱目录中,再预览沙箱目录中的pdf文件。
我这里用到了一个pdfviewer工具,可从我的百度网盘免费获取
拿到文件夹后,放在以下目录:
项目目录:harmonyApp\entry\src\main\resources\rawfile
具体实现代码如下:
import router from '@ohos.router';
import web_webview from '@ohos.web.webview';
import { BusinessError, request } from '@kit.BasicServicesKit';
import showToast from '../../common/utils/ToastUtils';
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { util } from '@kit.ArkTS';interface IBase64 {base64: string;fileName: string;
}@Entry
@Component
struct Index2 {controller: web_webview.WebviewController = new web_webview.WebviewController()// pdf文件路径@State fileUrl: string = ''// 本地沙箱文件地址@State tempFilePath: string = ''// 是否显示按钮@State isShowBtn: boolean = true;build() {Stack() {Column() {// 页面内容Scroll(){Column(){if(this.tempFilePath){if(this.isShowBtn){Button('打开文件').onClick(()=>{this.isShowBtn = false;})}else{Web({ src: $rawfile('pdfviewer/viewer.html'), controller: this.controller }).onProgressChange((event)=>{console.log("newProgress", event?.newProgress)}).domStorageAccess(true) // 设置是否开启文档对象模型存储接口(DOM Storage API)权限,默认未开启。.onPageEnd(()=>{let file = this.sandBoxPdfToBase64(this.tempFilePath);this.controller.runJavaScript(`openFile("${file.base64}", "${file.fileName}")`);})}}}.width('100%').height('100%')}.edgeEffect(EdgeEffect.Fade).width('100%').layoutWeight(1).align(Alignment.TopStart)}.height('100%').backgroundColor(Color.White)}}// 沙箱pdf文件转base64方法sandBoxPdfToBase64(url: string) {let file = fs.openSync(url, fs.OpenMode.READ_WRITE); // 打开文件let stat = fs.statSync(url); // 获取文件状态let buf = new ArrayBuffer(stat.size); // 创建一个ArrayBuffer对象let base64 = new util.Base64Helper(); // 实例化Base64Helperlet num = fs.readSync(file.fd, buf); // 读取文件let data = base64.encodeSync(new Uint8Array(buf.slice(0, num))) // 转换成Uint8Arraylet textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })let retStr = textDecoder.decodeWithStream(data, { stream: false }); // 可以把Uint8Array转码成base64let fileName = file.namefs.closeSync(file);return { base64: retStr, fileName: fileName } as IBase64;}// 下载pdf文件,获取沙箱文件目录getTempFile(fileUrl:string){let context = getContext(this) as common.UIAbilityContext;const fileFullName = fileUrl.split('/')[fileUrl.split('/').length - 1]let tempFilePath = `${context.filesDir}/${fileFullName}`;//文件如果已经存在,就删除if (fs.accessSync(tempFilePath)) {fs.unlink(tempFilePath)}request.downloadFile(getContext(), { url: fileUrl,filePath: tempFilePath }).then((data: request.DownloadTask) => {let downloadTask: request.DownloadTask = data;let progressCallback = (receivedSize: number, totalSize: number) => {// 这里可以自行编写下载进度条showToast(`下载大小${receivedSize},总大小${totalSize}`);};let completeCallback = ()=>{showToast("下载完毕");this.tempFilePath = tempFilePath;}downloadTask.on('progress', progressCallback);downloadTask.on('complete', completeCallback)}).catch((err: BusinessError) => {console.error(`Failed to request the download. Code: ${err.code}, message: ${err.message}`);})}// 组件生命周期:组件即将出现时回调该接口aboutToAppear() {console.log('进入页面')// 你的pdf链接this.fileUrl = (router.getParams() as Record<string, string>).url || '';this.getTempFile((router.getParams() as Record<string, string>).url as string);}
}
这里附有将pdf文件下载到沙箱目录代码,可选择使用(不必须)。
效果中的整体代码
import web_webview from '@ohos.web.webview';
import promptAction from '@ohos.promptAction'
import { BusinessError, request } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { util } from '@kit.ArkTS';// pdf页面tab接口
interface pageTab {name:string;
}interface IBase64 {base64: string;fileName: string;
}/*** pdfPage的ViewModel*/
class PdfPageModel {// 当前索引curTabIndex:number = 0;// pdf页面tabtabList:pageTab[] = [{ name:'预览本地PDF文件' },{ name:'预览网络PDF文件' },{ name:'预览沙箱PDF文件' },];// 网络文件fileUrl: string = 'http://www.cztouch.com/upfiles/soft/testpdf.pdf'// 本地沙箱文件地址tempFilePath: string = ''constructor() {}// 沙箱pdf文件转base64方法sandBoxPdfToBase64(url: string) {let file = fs.openSync(url, fs.OpenMode.READ_WRITE); // 打开文件let stat = fs.statSync(url); // 获取文件状态let buf = new ArrayBuffer(stat.size); // 创建一个ArrayBuffer对象let base64 = new util.Base64Helper(); // 实例化Base64Helperlet num = fs.readSync(file.fd, buf); // 读取文件let data = base64.encodeSync(new Uint8Array(buf.slice(0, num))) // 转换成Uint8Arraylet textDecoder = util.TextDecoder.create('utf-8', { ignoreBOM: true })let retStr = textDecoder.decodeWithStream(data, { stream: false }); // 可以把Uint8Array转码成base64let fileName = file.namefs.closeSync(file);return { base64: retStr, fileName: fileName } as IBase64;}// 下载pdf文件,获取沙箱文件目录getTempFile(fileUrl:string){let context = getContext(this) as common.UIAbilityContext;const fileFullName = fileUrl.split('/')[fileUrl.split('/').length - 1]let tempFilePath = `${context.filesDir}/${fileFullName}`;//文件如果已经存在,就删除if (fs.accessSync(tempFilePath)) {fs.unlink(tempFilePath)}request.downloadFile(getContext(), { url: fileUrl,filePath: tempFilePath }).then((data: request.DownloadTask) => {let downloadTask: request.DownloadTask = data;let progressCallback = (receivedSize: number, totalSize: number) => {// 这里可以自行编写下载进度条// showToast(`下载大小${receivedSize},总大小${totalSize}`);};let completeCallback = ()=>{// showToast("下载完毕");this.tempFilePath = tempFilePath;}downloadTask.on('progress', progressCallback);downloadTask.on('complete', completeCallback)}).catch((err: BusinessError) => {console.error(`Failed to request the download. Code: ${err.code}, message: ${err.message}`);})}// tab切换switchTab(index:number){this.curTabIndex = index;if(index === 2 && !this.tempFilePath){try {promptAction.showDialog({title: '温馨提示',message: '有些pdf线上链接是经过第三方加密过的,在浏览器访问时不能直接预览,直接走的是下载的pdf文件链接,可以采用这种方式,先下载在沙箱目录中,然后再预览沙箱中的pdf文件',buttons: [{text: '知道了',color: '#000000'}]}, (err, data) => {if (err) {console.error('showDialog err: ' + err);return;}console.info('showDialog success callback, click button: ' + data.index);});} catch (error) {console.error(`Failed to show dialog. Code: ${error.code}, message: ${error.message}`);}this.getTempFile(this.fileUrl);}}
}@Entry
@Component
struct PdfPage {webviewController: web_webview.WebviewController = new web_webview.WebviewController();@State vm: PdfPageModel = new PdfPageModel();// 验证是否选中VerifySelectedFun( curIndex:number , itemIndex:number ):boolean{return curIndex == itemIndex}aboutToAppear(): void {try {promptAction.showDialog({title: '温馨提示',message: '在模拟器中运行,首次加载会出现黑屏,但来回切换几次tab标签就好了,有条件的建议使用真机运行,不会有这样的问题',buttons: [{text: '知道了',color: '#000000'}]}, (err, data) => {if (err) {console.error('showDialog err: ' + err);return;}console.info('showDialog success callback, click button: ' + data.index);});} catch (error) {console.error(`Failed to show dialog. Code: ${error.code}, message: ${error.message}`);}}build() {Stack() {Column() {// tab标签条Row(){Scroll(){Row(){ForEach(this.vm.tabList,(item:pageTab,index)=>{Row(){if(this.VerifySelectedFun(this.vm.curTabIndex,index)){Stack(){Row(){}.width(40).height(10).borderRadius(20).offset({y:7}).linearGradient({angle:89.11,colors:[['rgba(255, 255, 255, 0.55)',0.0682],['rgba(217, 217, 217, 0)',1]]})Text(item.name).fontSize(18).fontColor($r('app.color.primary_theme_color')).fontWeight(600).height('100%')}}else{Text(item.name).fontSize(16)// .fontColor($r('app.color.font_color_default')).fontWeight(400)}}.height('100%').justifyContent(FlexAlign.Start).padding({ left: index == 0 ? 0 : 20 }).onClick(()=>{this.vm.switchTab(index)})})}}.edgeEffect(EdgeEffect.Fade).layoutWeight(1).align(Alignment.Center).scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)}.width('100%').height(50).justifyContent(FlexAlign.Start).padding({left:16,right:16}).backgroundColor(Color.White)// 页面内容Scroll(){Column(){if(this.vm.curTabIndex === 0 ){// web组件加载本地pdf文件Web({ src: $rawfile('Git.pdf'), controller: this.webviewController}).domStorageAccess(true).onProgressChange((event)=>{console.log("newProgress", event?.newProgress)})}else if(this.vm.curTabIndex === 1){// web组件加载网络pdf文件Web({ src: 'http://www.cztouch.com/upfiles/soft/testpdf.pdf', controller: this.webviewController }).layoutWeight(1).domStorageAccess(true).onProgressChange((event)=>{console.log("newProgress", event?.newProgress)})}else if(this.vm.curTabIndex === 2){if(this.vm.tempFilePath){Web({ src: $rawfile('pdfviewer/viewer.html'), controller: this.webviewController }).onProgressChange((event)=>{console.log("newProgress", event?.newProgress)}).domStorageAccess(true) // 设置是否开启文档对象模型存储接口(DOM Storage API)权限,默认未开启。.onPageEnd(()=>{let file = this.vm.sandBoxPdfToBase64(this.vm.tempFilePath);this.webviewController.runJavaScript(`openFile("${file.base64}", "${file.fileName}")`);})}}}.padding({ left: 16, right: 16, bottom: 16 })}.edgeEffect(EdgeEffect.Fade).width('100%').layoutWeight(1).align(Alignment.TopStart)}.height('100%').backgroundColor('#F5F5F5').padding({ bottom: 16 })}}
}
总结
总体来说就是使用Web组件加载pdf文件,在模拟器中运行,首次运行会黑屏,不过来回切换一下tab页就好了,真机运行没有问题。
为啥要存到沙箱中再预览,岂不是多此一举?
当然不是,因为有的pdf文件是通过第三方加密过的,在浏览器打开链接时,是不能直接预览的,而是直接走下载了。这时,就需要先存到沙箱目录中再预览。
有需要的朋友,拿走不谢,求赞,求赞,求赞~
关注我不迷路,不定时分享鸿蒙难点亮点
Harmony OS NEXT版本(接口及解决方案兼容API12版本或以上版本)
相关文章:
鸿蒙ArkTs如何实现pdf预览功能?
鸿蒙ArkTs如何实现pdf预览功能? 前言PDFKit运行示例代码报错真机运行先看效果一、预览本地pdf文件二、预览线上的pdf文件三、预览沙箱目录中pdf的文件(重点)效果中的整体代码总结 Harmony OS NEXT版本(接口及解决方案兼容API12版本或以上版本) 前言 在开…...
KylinSP3 | 防火墙和麒麟安全增强设置KySec
一、系统防火墙原理 麒麟操作系统从V10版本开始,默认使用了Firewalld防火墙,Firewalld是能提供动态管理的防火墙,支持网络/防火墙区域,用于定义网络连接或接口的信任级别。支持IPv4和IPv6防火墙设置、以太网桥接和IP集。将运行时…...
【C++】面试常问八股
5、内存管理 野指针 野指针指的是未进行初始化或未清零的指针,不是NULL指针野指针产生原因及解决方案: 指针变量未初始化:指针变量定义时若未初始化,则其指向的地址是随机的,不为NULL;定义时初始化为NULL…...
vscode多文件编译构建(CMake)和调试C++
目录 1. CMake 基础构建工具及作用相关配置文件 2. 配置 tasks.json关键字段详细解释 3. 配置 launch.json关键字段详细解释 4. 配置 CMakeLists.txt关键部分详细解释 5. 构建和调试项目1. 仅构建项目1.1 任务执行顺序1.2 cmake 任务执行详情1.3 build 任务执行详情1.4 构建后的…...
使用Docker 部署 LNMP+Redis 环境
使用Docker 部署 LNMPRedis 环境 Docker 简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互…...
文件上传漏洞学习笔记
一、漏洞概述 定义 文件上传漏洞指未对用户上传的文件进行充分安全校验,导致攻击者可上传恶意文件(如Webshell、木马),进而控制服务器或执行任意代码。 危害等级 ⚠️ 高危漏洞(通常CVSS评分7.0)ÿ…...
375_C++_cloud手机推送,添加人脸告警信息到任务队列中,UploadAlarmPush是典型的工厂模式应用,为什么使用工厂模式完成这部分代码
一:AlarmFaceInfo的应用 让我帮你解析这个lambda表达式的实现: // ...................... .h ...........................// struct RsMsgPushTask_S : public Task{AlarmType_E mainAlarmType;unsigned int subAlarmType;DateTime alarmTime...
Spring Boot 中的日志管理
一、日志框架选择 1. 主流框架对比 框架特点Spring Boot 默认支持Logback- 性能优异,Spring Boot 默认集成- 支持自动热更新配置文件✅ (默认)Log4j2- 异步日志性能更强- 支持插件扩展- 防范漏洞能力更好❌ (需手动配置)JUL (JDK自带)- 无需额外依赖- 功能简单&am…...
火绒终端安全管理系统V2.0网络防御功能介绍
火绒终端安全管理系统V2.0 【火绒企业版V2.0】网络防御功能包含网络入侵拦截、横向渗透防护、对外攻击检测、僵尸网络防护、Web服务保护、暴破攻击防护、远程登录防护、恶意网址拦截。火绒企业版V2.0的网络防御功能,多层次、多方位,守护用户终端安全。 …...
海康摄像头 + M7s(Monibuca) + FFmpeg + Python实现多个网络摄像头视频流推流
最近在研究流媒体服务器时,我注意到了一款开源软件——M7s。按照官网的指南部署完成后,我开始进行测试,发现单视频流推送非常顺利,没有任何问题。然而,当我尝试进行多视频流推送时,却发现网上的相关教程寥寥…...
抖音视频如何下载保存去水印
随着短视频平台的兴起,抖音作为国内最受欢迎的短视频平台之一,吸引了大量用户上传和观看各种创意视频。许多用户在浏览抖音视频时,往往会想要保存一些有趣或精彩的视频片段,但抖音视频通常会有水印,影响观看体验。为了…...
【鸿蒙开发】第三十九章 LazyForEach:数据懒加载
目录 1 背景 2 使用限制 键值生成规则 组件创建规则 首次渲染 非首次渲染 改变数据子属性 使用状态管理V2 拖拽排序 1 背景 LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架…...
HTTP-
一.HTTP 1.什么是HTTP HTTP(超文本传输协议)是一种工作在应用层的协议.主要用于网站,就是浏览器和服务器之间的数据传输. 小知识:什么是超文本传输协议 文本:是字符串.(能在utf8/gbk码表上找到合法字符) 超文本:不仅可以传输字符串,也可以传输图片,html等 富文本:word文档 2.HT…...
创建型模式 - 原型模式 (Prototype Pattern)
创建型模式 -原型模式 (Prototype Pattern) 它允许通过复制现有对象来创建新对象,而无需知道对象的具体创建细节。在 Java 中,可以通过实现 Cloneable 接口和重写 clone() 方法来实现原型模式。 有深、浅两种克隆 类实现 Cloneable 接口就可以深克隆如果…...
Android 8.0 (API 26) 对广播机制做了哪些变化
大部分隐式广播无法通过静态注册接收,除了以下白名单广播: ACTION_BOOT_COMPLETED ACTION_TIMEZONE_CHANGED ACTION_LOCALE_CHANGED ACTION_MY_PACKAGE_REPLACED ACTION_PACKAGE_ADDED ACTION_PACKAGE_REMOVED 需要以动态注册方案替换: cl…...
Unity汽车笔记
汽车的移动和转向 我们知道,汽车的前进后退是变速运动。按w,汽车开始加速,到最大速度后保持匀速,松开w,汽车受到阻力加速。如果按s减速,则以更大的加速度减速。后退反之。 按A/D时前轮偏转。只有前进后退…...
html中rel、href、src、url的区别
1.url url(统一资源定位符):是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。 2.href href:Hypertext Reference的缩写。 意思是超文本引用。 3.rel rel:relatio…...
【idea问题排查技巧】
以下是针对 IDEA 中 日志打标(动态标记) 和 全链路追踪 功能的分步详解,结合具体场景和操作截图说明,帮助快速掌握实战技巧。 一、动态日志打标:不修改代码输出关键信息 1. 断点日志打印(非侵入式打标) 场景:在调试时,需要临时查看某个变量的值,但不想修改代码添加…...
SQL: DDL,DML,DCL,DTL,TCL,
Structured Query Language,结构化查询语言, 是一种用于管理和操作关系数据库的标准编程语言。 sql的分类 DQL(Data Query Language):数据查询语言 DDL(Data Definition Language):数据定义语…...
WordPress R+L Carrier Edition sql注入漏洞复现(CVE-2024-13481)(附脚本)
免责申明: 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x0…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...
