鸿蒙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…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
Spring Boot 实现流式响应(兼容 2.7.x)
在实际开发中,我们可能会遇到一些流式数据处理的场景,比如接收来自上游接口的 Server-Sent Events(SSE) 或 流式 JSON 内容,并将其原样中转给前端页面或客户端。这种情况下,传统的 RestTemplate 缓存机制会…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...