uni-app vue 在手机APP中将页面转为图片、保存为PDF文件、打开文件

该博客主要介绍了在uni-app中生成图片并将其转换为base64格式文件,以及在安卓系统下将base64转换为文件的方法。内容包含安装插件、设置script节点、引入插件、编写HTML内容等步骤,并给出了完整演示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、安装插件

npm i html2canvas
npm i jspdf

二 、将第一个script 节点的 lang 为 renderjs

<script module="test" lang="renderjs"></script>

三、引入插件

	import html2canvas from 'html2canvas'
	import { jsPDF } from 'jspdf';

四、HTML内容

		<uni-table stripe emptyText="暂无更多数据" id="detail">
			<uni-tr>
				<uni-td>样品名称</uni-td>
				<uni-td>456</uni-td>
			</uni-tr>
			<uni-tr>
				<uni-td>日期</uni-td>
				<uni-td>2023-04-24</uni-td>
			</uni-tr>
			<uni-tr>
				<uni-td>单位</uni-td>
				<uni-td>单位</uni-td>
			</uni-tr>
		</uni-table>

五、生成图片并获取base64格式文件

const detail = document.getElementById(
	'detail') //获取到html包含此页面的外层标签,detail为页面中需要导出为pdf的最外层标签的id名
const imgHeight = detail.clientHeight;
const imgWidth = detail.clientWidth;

html2canvas(detail,{//对应的dom元素id(class也可以)
	allowTaint: true,//是否允许跨域图像渲染画布
	useCORS:true,//是否尝试使用 CORS 从服务器加载图像 解决图片跨域问题
}).then((canvas) => {
	// console.log(canvas);
	return new Promise((resolve, reject) => {
		console.log(4);
		setTimeout(() => {
			console.log(5);
			resolve(canvas)
		}, 500) 
	}).then((canvas) => {
		console.log(6);
		
		//生成的canvas实例
		var contentWidth = canvas.width;//所选元素宽度
		var contentHeight = canvas.height;//所选元素高度
		//一页pdf显示html页面生成的canvas高度;
		var pageHeight = contentWidth / 595.28 * 841.89;
		//未生成pdf的html页面高度
		var leftHeight = contentHeight;
		//pdf页面偏移
		var position = 0;
		//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
		var imgWidth = 555.28;
		var imgHeight = 555.28 / contentWidth * contentHeight;
		var pageData = canvas.toDataURL('image/jpeg', 1.0);//转成jpg格式
		var pdf = new jsPDF('', 'pt', 'a4');//生成pdf实例
		//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
		//当内容未超过pdf一页显示的范围,无需分页
		if (leftHeight < pageHeight) {
			pdf.addImage(pageData, 'JPEG', 20, 0, imgWidth, imgHeight);
		} else {
			while (leftHeight > 0) {
				pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight)
				leftHeight -= pageHeight;
				position -= 841.89;
				//避免添加空白页
				if (leftHeight > 0) {
					pdf.addPage();
				}
			}
		}

		var blob = pdf.output("datauristring");
        
        // 在renderjs中无法使用uni和plus对象,这里调用另一个script中的方法
		this.$ownerInstance.callMethod('downPdf', blob);
		
	}).catch((r) => {
		console.log(r);
	})
});

六、再写一个普通的script

<script></script>

七、定义base64转文件方法(仅支持安卓)

/**
 * base64字符串转成文件
 * @param {String} base64Str // 允许包含前缀
 * @param {String} fileName // 文件名称:1663061363470.xlsx
 * @param {Object} callback  // 返回本地路径径URL,file:///xxx/doc/1663062980631.xlsx
 */
function base64ToFile(base64Str, fileName, callback) {

	// 去除base64前缀
	var index = base64Str.indexOf(',')
	var base64Str = base64Str.slice(index + 1, base64Str.length)

	plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {
		fs.root.getFile(fileName, {
			create: true
		}, function(entry) {

			// 获得本地路径URL,file:///xxx/doc/1663062980631.xlsx
			var fullPath = entry.fullPath;

			var Base64 = plus.android.importClass("android.util.Base64");
			var FileOutputStream = plus.android.importClass("java.io.FileOutputStream");
			var out = new FileOutputStream(fullPath);
			// 此处Base64.decode有长度限制,如果不能满足需求,可以考虑换成官方原生插件市场的【Base64转文件】
			var bytes = Base64.decode(base64Str, Base64.DEFAULT);
			out.write(bytes);
			out.close();
			// 回调  
			callback && callback(entry.toLocalURL());
		})
	})
}

八、在export default中定义要被调用的方法

export default {
	methods: {
		downPdf(path) {
			var fileName = (new Date()).valueOf() + '.pdf';
			base64ToFile(path, fileName, function(path) {
				console.log('result', path);
				plus.runtime.openFile(path); //用第三方程序打开文件
			})
		}
	}
}

九、完整演示

<template>
	<view class="content">
		<view @click="test.myprint()">
			打印
		</view>
		<uni-table stripe emptyText="暂无更多数据" id="detail">
			<uni-tr>
				<uni-td>样品名称</uni-td>
				<uni-td>456</uni-td>
			</uni-tr>
			<uni-tr>
				<uni-td>日期</uni-td>
				<uni-td>2023-04-24</uni-td>
			</uni-tr>
			<uni-tr>
				<uni-td>单位</uni-td>
				<uni-td>单位</uni-td>
			</uni-tr>
		</uni-table>
	</view>
</template>
<script module="test" lang="renderjs">
	import html2canvas from 'html2canvas'
	import {
		jsPDF
	} from 'jspdf';
	export default {
		methods: {
			myprint() {
				const detail = document.getElementById(
					'detail') //获取到html包含此页面的外层标签,detail为页面中需要导出为pdf的最外层标签的id名
				const imgHeight = detail.clientHeight;
				const imgWidth = detail.clientWidth;

				html2canvas(detail, { //对应的dom元素id(class也可以)
					allowTaint: true, //是否允许跨域图像渲染画布
					useCORS: true, //是否尝试使用 CORS 从服务器加载图像 解决图片跨域问题
				}).then((canvas) => {
					// console.log(canvas);
					return new Promise((resolve, reject) => {
						console.log(4);
						setTimeout(() => {
							console.log(5);
							resolve(canvas)
						}, 500)
					}).then((canvas) => {
						console.log(6);

						//生成的canvas实例
						var contentWidth = canvas.width; //所选元素宽度
						var contentHeight = canvas.height; //所选元素高度
						//一页pdf显示html页面生成的canvas高度;
						var pageHeight = contentWidth / 595.28 * 841.89;
						//未生成pdf的html页面高度
						var leftHeight = contentHeight;
						//pdf页面偏移
						var position = 0;
						//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
						var imgWidth = 555.28;
						var imgHeight = 555.28 / contentWidth * contentHeight;
						var pageData = canvas.toDataURL('image/jpeg', 1.0); //转成jpg格式
						var pdf = new jsPDF('', 'pt', 'a4'); //生成pdf实例
						//有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
						//当内容未超过pdf一页显示的范围,无需分页
						if (leftHeight < pageHeight) {
							pdf.addImage(pageData, 'JPEG', 20, 0, imgWidth, imgHeight);
						} else {
							while (leftHeight > 0) {
								pdf.addImage(pageData, 'JPEG', 20, position, imgWidth, imgHeight)
								leftHeight -= pageHeight;
								position -= 841.89;
								//避免添加空白页
								if (leftHeight > 0) {
									pdf.addPage();
								}
							}
						}

						var blob = pdf.output("datauristring");
						console.log(7);
						// 在renderjs中无法使用uni和plus对象,这里调用另一个script中的方法
						this.$ownerInstance.callMethod('downPdf', blob);

					}).catch((r) => {
						console.log(r);
					})
				});
			}
		}
	}
</script>
<script>
	export default {
		data() {
			return {
				title: 'Hello'
			}
		},
		onLoad() {

		},
		methods: {
			/**
			 * base64字符串转成文件
			 * @param {String} base64Str // 允许包含前缀
			 * @param {String} fileName // 文件名称:1663061363470.xlsx
			 * @param {Object} callback  // 返回本地路径径URL,file:///xxx/doc/1663062980631.xlsx
			 */
			base64ToFile(base64Str, fileName, callback) {

				// 去除base64前缀
				var index = base64Str.indexOf(',')
				var base64Str = base64Str.slice(index + 1, base64Str.length)

				plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {
					fs.root.getFile(fileName, {
						create: true
					}, function(entry) {

						// 获得本地路径URL,file:///xxx/doc/1663062980631.xlsx
						var fullPath = entry.fullPath;

						var Base64 = plus.android.importClass("android.util.Base64");
						var FileOutputStream = plus.android.importClass("java.io.FileOutputStream");
						var out = new FileOutputStream(fullPath);
						// 此处Base64.decode有长度限制,如果不能满足需求,可以考虑换成官方原生插件市场的【Base64转文件】
						var bytes = Base64.decode(base64Str, Base64.DEFAULT);
						out.write(bytes);
						out.close();
						// 回调  
						callback && callback(entry.toLocalURL());
					})
				})
			},
			downPdf(path) {
				var fileName = (new Date()).valueOf() + '.pdf';
				this.base64ToFile(path, fileName, function(path) {
					console.log('result', path);
					plus.runtime.openFile(path); //用第三方程序打开文件
				})
			}
		}
	}
</script>

<!-- pages/webview/webview.vue --> <template> <view class="webview-container"> <cover-view class="webview-wrapper"> <web-view :src="webviewPath" id="targetWebview"></web-view> </cover-view> <cover-view class="bottom-action-area"> <cover-view class="capture-btn" @click="captureWebview"> <cover-view class="btn-text">立即取证</cover-view> </cover-view> </cover-view> <!-- 预览组件 --> <cover-view v-if="showPreview" class="custom-preview"> <cover-view class="preview-header"> <cover-view class="title-container"> <cover-view class="preview-title">预览界面</cover-view> </cover-view> </cover-view> <cover-image :src="screenshotPath" class="preview-image" mode="aspectFit" @error="handleImageError" ></cover-image > <cover-view class="action-buttons"> <cover-view class="action-btn cancel-btn" @click="handleAction('放弃')">放弃</cover-view> <cover-view class="action-btn confirm-btn" @click="handleAction('固化')">固化</cover-view> </cover-view> <cover-view> </cover-view> </cover-view> </view> </template> <script setup lang="ts"> import { onLoad, onReady } from '@dcloudio/uni-app'; import html2canvas from 'html2canvas'; import { ref } from 'vue'; declare const plus: any; const webviewPath = ref(''); const ws = ref<any>(null); const screenshotPath = ref(''); const showPreview = ref(false); const platform = ref(''); const originalWebviewHeight = ref('auto'); const screenshotFilePath = ref(''); onLoad((options: any) => { if (options.url) { webviewPath.value = decodeURIComponent(options.url); } // 获取当前平台 const systemInfo = uni.getSystemInfoSync(); platform.value = systemInfo.platform; }); onReady(() => { const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const currentWebview = currentPage.$getAppWebview(); setTimeout(() => { if (currentWebview.children().length > 0) { const wv = currentWebview.children()[0]; ws.value = wv; } }, 1000); }); const captureWebview = async () => { if (!ws.value) { uni.showToast({ title: 'WebView未准备好', icon: 'none' }); return; } uni.showLoading({ title:'正在取证中' }) // #ifdef APP-PLUS // #endif // #ifdef APP-H5 // #endif } const closePreview = () => { showPreview.value = false; } const handleAction = (action: string) => { switch(action) { case '放弃': closePreview(); break; case '固化': uni.saveImageToPhotosAlbum({ filePath: screenshotPath.value, success: () => { uni.showToast({ title: '证据已固化保存' }); // 调用上传函数 // uploadEvidence(); }, fail: (err) => { uni.showToast({ title: '保存失败: ' + err.errMsg, icon: 'none' }); } }); break; } } const handleImageError = (e: any) => { console.error('图片加载错误:', e); uni.showToast({ title: '图片加载错误', icon: 'none' }); } </script> <style> .webview-container { position: relative; width: 100%; height: 100vh; overflow: hidden; } .webview-wrapper { height: calc(100vh - 120rpx); width: 100%; overflow: hidden; } .bottom-action-area { position: fixed; bottom: 0; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; padding-bottom: constant(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom); display: flex; justify-content: center; align-items: center; } .capture-btn { width: 100%; height: 90rpx; background-color: #007aff; border-radius: 45rpx; box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2); display: flex; align-items: center; justify-content: center; } .btn-text { color: #ffffff; font-size: 32rpx; font-weight: 500; } /* 优化预览组件 */ .custom-preview { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; background-color: rgba(0, 0, 0, 0.9); display: flex; flex-direction: column; /* 垂直排列子元素 */ align-items: center; /* 关键修改:去掉顶部内边距,只保留底部内边距 */ padding: 0 0 40rpx 0; box-sizing: border-box; } .preview-header { width: 100%; height: 250rpx; /* 增加高度 */ background-color: #007aff; color: #ffffff; font-size: 34rpx; /* 增大字体 */ font-weight: bold; display: flex; align-items: center; justify-content: center; /* 添加圆角效果 */ border-top-left-radius: 16rpx; border-top-right-radius: 16rpx; } .preview-title { color: #ffffff; /* 白色字体 */ font-size: 28rpx; /* 字体大小适配40rpx高度 */ font-weight: bold; line-height: 40rpx; /* 与容器高度一致,实现垂直居中 */ width: 100%; text-align: center; } .preview-image { width: 100%; height: 100%; object-fit: contain; border: 1rpx solid rgba(255, 255, 255, 0.1); border-radius: 8rpx; background-color: #f5f5f5; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3); } .action-buttons { display: flex; width: 100%; /* 充满整个宽度 */ justify-content: space-between; padding: 30rpx 5%; /* 左右留5%间隙,上下30rpx内边距扩展背景区域 */ margin-top: auto; /* 借助flex布局推到最底部 */ background-color: #353336; /* 底部模块背景色 */ box-sizing: border-box; /* 确保padding不影响宽度计算 */ } .action-btn { flex: 1; height: 90rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; padding: 0; margin: 0 15rpx; border: none; color: #ffffff; /* 文字色适配深色背景 */ font-size: 34rpx; font-weight: 500; line-height: 1; } .cancel-btn { color: #ffffff; /* 文字颜色 */ font-size: 34rpx; font-weight: 500; /* 确保文字本身无偏移 */ line-height: 1; /* 清除行高影响 */ text-align: center; /* 辅助居中(冗余保障) */ background-color: #353336; } .confirm-btn { color: #ffffff; /* 文字颜色 */ font-size: 34rpx; font-weight: 500; /* 确保文字本身无偏移 */ line-height: 1; /* 清除行高影响 */ text-align: center; /* 辅助居中(冗余保障) */ background-color: #353336; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .bottom-action-img{ position: fixed; top: 100; left: 0; right: 0; z-index: 100; background-color: #007aff; padding: 30rpx; display: flex; justify-content: center; align-items: center; } .action-img{ width: 100%; height: 90rpx; background-color: #007aff; display: flex; } </style> app设备中 将webview内容换成pdf格式
最新发布
08-09
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓_枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值