Uniapp 适配安卓与 iOS 的 PDF、DOC 文件上传

一、上传文件

1、配置权限

首先需要在manifest.json中配置相应的读写本地文件的权限

javascript 复制代码
"app-plus" : {
    "webview" : {
        "allowFileAccess" : true,
        "allowFileAccessFromFileURLs" : true,
        "allowUniversalAccessFromFileURLs" : true,
        "hardwareAccelerated" : true
    },
    /* 权限配置 - 新增 */
    "permissions" : {
        "File" : {},
        "Runtime" : {},
        "Cache" : {},
        "Device" : {},
        "Gallery" : {},
        "NativeUI" : {},
        "NativeObj" : {},
        "IO" : {},
        "fileSystem" : {
            "description" : "访问文件系统"
        }
    },
"modules" : {
    "Camera" : {},
    "Barcode" : {},
    "filemanager" : {}
  },
    "distribute" : {
            /* android打包配置 */
            "android" : {
                "permissions" : [
                    "<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
                    "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
                    "<uses-permission android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\" android:minSdkVersion=\"30\"/>",
                    "<uses-permission android:name=\"android.permission.ACCESS_MEDIA_LOCATION\"/>",
                    "<uses-permission android:name=\"android.permission.INTERNET\"/>"
                ],
                /* Android 11+ 文件访问适配 - 新增 */
                "android:requestLegacyExternalStorage" : true,
                /* 文件提供者配置 - 新增 */
                "application" : {
                    "meta-data" : [
                        {
                            "name" : "android.support.FILE_PROVIDER_PATHS",
                            "value" : "@xml/file_paths"
                        }
                    ]
                }
            }
        },
        "uniStatistics" : {
            "enable" : false
        }
    },

2、安卓上传

下面是安卓上传文件步骤(这里用pdf举例)

javascript 复制代码
chooseFile() {
	plus.io.chooseFile({
		title: '选择文件',
		filetypes: ['pdf'],
		multiple: false,
	}, async (e) => {
		try {
			const filePath = e.files[0];
			const fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
			const fileExt = fileName.split('.').pop()?.toLowerCase();
			// 校验后缀是否为pdf
			if (fileExt !== 'pdf') {
				uni.showToast({
					title: '仅支持上传PDF格式文件',
					icon: 'none',
					duration: 2000
				});
				return; // 终止后续操作
			}
			plus.io.resolveLocalFileSystemURL(e.files[0], async (
				entry) => {
				const realPath = entry.toURL().startsWith('file://') ?
					entry.toURL() :
					'file://' + entry.toURL();
				const mimeType = entry.name.endsWith('.pdf') ?
					'application/pdf' :
					'application/octet-stream';
				const item = {
					url: realPath,
					filename: entry.name
				}
                console.log(realPath,'本地虚拟文件路径');// 在这里拿到realPath后就可以调用oss直传接口
				
			}, (err) => {
				// 错误处理
				const rawPath = e.files[0];
				const finalPath = rawPath.startsWith('file://') ? rawPath : 'file://'
					rawPath;
				// 可以在这里 fallback 处理
			});
		} catch (error) {
			console.error('异步操作出错:', error);
			uni.showToast({
				title: '操作失败',
				icon: 'none'
			});
		}
	});
},

3、ios上传

ios上传是通过webview加载本地静态html实现

uniapp webview+本地静态html+base 64文件传递混合上传方案

把下面这个html直接放在static目录下,注意需要引入 uni.webview.js,可以使用cdn或者下载放本地直接引用,放在本地直接引用加载速度更快,推荐下载下来直接引入使用
https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>选择 PDF 文件</title>
		<style>
			body {
				margin: 0;
				padding: 20px;
				font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
			}

			.upload-btn {
				width: 100%;
				padding: 15px;
				background: #007aff;
				color: white;
				border: none;
				border-radius: 8px;
				font-size: 18px;
				cursor: pointer;
				transition: background 0.2s;
			}

			.upload-btn:active {
				background: #0066cc;
			}

			.status {
				margin-top: 20px;
				text-align: center;
				color: #666;
				font-size: 16px;
				line-height: 1.5;
			}
		</style>
		<!-- 引入 uni.webview.js(必须) -->
		<!-- <script src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></script> -->
		<script src="./js/uni.webview.1.5.2.js"></script>
	</head>
	<body>
		<input type="file" id="fileInput" accept=".pdf,application/pdf" style="display: none;" />
		<button class="upload-btn" onclick="selectPdf()">选择 PDF 文件</button>
		<div class="status" id="status">未选择文件</div>

		<script>
			// 状态管理
			let uniReady = false;
			let pendingFile = null; // 暂存用户选中的文件(用于 uni 未就绪时)
			const fileInput = document.getElementById('fileInput');
			const statusText = document.getElementById('status');

			// 监听 uni 初始化完成
			document.addEventListener('UniAppJSBridgeReady', function() {
				console.log('✅ uni 对象已初始化完成,可安全使用 uni.postMessage');
				uniReady = true;

				// 如果有暂存的文件,立即发送
				if (pendingFile) {
					statusText.textContent = '系统初始化完成,正在发送之前选择的文件...';
					sendByPostMessage(pendingFile.base64Data, pendingFile.file);
					pendingFile = null;
				}
			});

			// 触发文件选择
			function selectPdf() {
				fileInput.click();
			}

			// 文件选择监听
			fileInput.addEventListener('change', function(e) {
				const file = e.target.files[0];
				if (!file) return;

				// 校验是否为 PDF
				if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) {
					statusText.textContent = '❌ 请选择 PDF 格式文件';
					return;
				}

				statusText.textContent = `📄 正在读取文件:${file.name}`;
				const reader = new FileReader();

				reader.onload = function(event) {
					const base64Data = event.target.result;
					statusText.textContent = `✅ 文件读取完成,准备传递给 App...`;

					// 尝试发送(内部会判断 uni 是否 ready)
					sendByPostMessage(base64Data, file);
				};

				reader.onerror = function() {
					statusText.textContent = '❌ 文件读取失败,请重试';
					console.error('WebView 文件读取失败');
				};

				reader.readAsDataURL(file);
			});

			// 安全发送消息(核心逻辑)
			function sendByPostMessage(base64Data, file) {
				if (!uniReady) {
					// uni 还没 ready,暂存文件
					console.warn('⚠️ uni 未就绪,暂存文件待发送');
					pendingFile = {
						base64Data,
						file
					};
					statusText.textContent = '⏳ 系统初始化中,文件已暂存,稍后自动发送...';
					return;
				}

				try {
					const message = {
						type: 'pdfSelected',
						fileName: file.name,
						fileSize: file.size,
						base64: base64Data
					};

					// 使用 uni.postMessage 发送(uni-app WebView 专用)
					uni.postMessage({
						data: {
							message: message
						}
					});
					statusText.textContent = '📤 消息已发送,请等待 App 处理...';

					// 清空 input,避免重复选择相同文件无触发
					fileInput.value = '';
				} catch (error) {
					console.error('❌ uni.postMessage 发送失败:', error);
					statusText.textContent = '💥 消息发送失败,请返回重试';
				}
			}

			// (可选)接收 App 回传状态(如果 App 会 postMessage 回来)
			window.addEventListener('message', function(event) {
				const data = event.data;
				if (data && data.type === 'uploadStatus') {
					statusText.textContent = data.msg;
				}
			});
		</script>
	</body>
</html>

将文件选择(<input type="file">)和读取逻辑完全放在一个 本地静态 HTML 页面 中,利用浏览器原生能力触发系统文件选择器(尤其在 App 端比直接在 Vue 页面中更可靠)。

这里是写成一个组件,可直接在页面复用

html 复制代码
<template>
	<view class="container">
		<!-- 上传 -->
		<web-view :src="webviewUrl" @message="onWebviewMessage" class="webview"></web-view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				webviewUrl: plus.io.convertLocalFileSystemURL('/static/upload-pdf.html'),
				originalFileName: '',
			};
		},
		methods: {
			// 监听WebView消息
			onWebviewMessage(e) {
				try {
					let base64 = null;
					let fileName = '';
					if (e.detail && e.detail.data && e.detail.data[0] && e.detail.data[0].message) {
						base64 = e.detail.data[0].message.base64;
						fileName = e.detail.data[0].message.fileName || '';
					}
					if (base64) {
						console.log('开始处理PDF文件,原始文件名:', fileName);
						this.processPdfFile(base64, fileName);
					}
				} catch (err) {
					console.error('消息解析失败:', err);
					uni.hideLoading();
				}
			},
			processPdfFile(base64Data, originalFileName) {
				uni.showLoading({
					title: '处理中...',
					mask: true
				});
				this.originalFileName = originalFileName;
				console.log('使用uni.downloadFile方案处理Base64数据...');
				const base64Url = base64Data;
				uni.downloadFile({
					url: base64Url,
					success: async (res) => {
						if (res.statusCode === 200) {
							console.log('✅ 临时文件下载成功,路径:', res.tempFilePath);
							const isValid = await this.validateTempFile(res.tempFilePath);
							if (isValid) {
								// 在这里获取到虚拟路径就可以直接调用oss直传接口
							} else {
								uni.showToast({
									title: '文件验证失败',
									icon: 'error'
								});
							}
						} else {
							console.error('下载失败,状态码:', res.statusCode);
							uni.showToast({
								title: '文件处理失败',
								icon: 'error'
							});
						}
						uni.hideLoading();
					},
					fail: (err) => {
						console.error('下载失败:', err);
						uni.hideLoading();
						uni.showToast({
							title: '处理失败',
							icon: 'error'
						});
					}
				});
			},
			// 验证临时文件
			validateTempFile(tempFilePath) {
				return new Promise((resolve) => {
					plus.io.resolveLocalFileSystemURL(tempFilePath, (entry) => {
						entry.file((file) => {
							console.log(`📊 文件大小: ${file.size} 字节`);
							if (file.size < 100) {
								console.warn('⚠️ 文件大小异常,可能损坏');
								resolve(false);
								return;
							}
							const reader = new plus.io.FileReader();
							reader.onload = function(e) {
								const firstBytes = e.target.result.substr(0, 20);
								console.log('文件头信息:', firstBytes);
								if (firstBytes.includes('%PDF')) {
									console.log('确认是有效的PDF文件');
									resolve(true);
								} else {
									console.warn('文件头不是PDF格式');
									resolve(true);
								}
							};
							reader.onerror = function() {
								console.warn('无法读取文件头,但文件大小正常,继续上传');
								resolve(true);
							};
							reader.readAsText(file.slice(0, 100));
						}, (err) => {
							console.error('无法读取文件:', err);
							resolve(false);
						});
					}, (err) => {
						console.error('文件路径无效:', err);
						resolve(false);
					});
				});
			},
		}
	};
</script>

<style scoped>
	.container {
		width: 100%;
		height: 100vh;
	}
	.webview {
		width: 100%;
		height: 100%;
	}
</style>
相关推荐
如此风景3 小时前
IOS开发SwiftUI相关学习记录
ios
モンキー・D・小菜鸡儿3 小时前
Android 系统TTS(文字转语音)解析
android·tts
2501_915909063 小时前
iOS 反编译防护工具全景解析 从底层符号到资源层的多维安全体系
android·安全·ios·小程序·uni-app·iphone·webview
Swizard3 小时前
速度与激情:Android Python + CameraX 零拷贝实时推理指南
android·python·ai·移动开发
summerkissyou19874 小时前
Android13-Audio-AudioTrack-播放流程
android·音视频
里纽斯4 小时前
RK平台Watchdog硬件看门狗验证
android·linux·rk3588·watchdog·看门狗·rk平台·wtd
lvha5 小时前
uniapp BLE低功耗蓝牙插件 支持安卓 iOS 鸿蒙NEXT 微信小程序
uni-app·蓝牙
三七吃山漆5 小时前
攻防世界——comment
android·python·web安全·网络安全·ctf
用户413079810615 小时前
终于懂了-ARouter原理初探
android
fatiaozhang95275 小时前
晶晨S905L3B芯片-2+8G-安卓9.0-ATV原生设置(深度精简优化)-通刷-线刷固件包
android·电视盒子·刷机固件·机顶盒刷机·晶晨s905l3b通刷包·e900v22c-s905l3