ZaiZai 1 year ago
parent
commit
e7b7160f93

+ 52 - 112
components/select-file/index.vue

@@ -1,134 +1,74 @@
 <template>
-    <web-view
-        ref="webViewRef"
-        :update-title="false"
-        :webview-styles="webViewStyle"
-        :style="webViewStyle"
-        :src="`/hybrid/html/select-file/index.html?accept=${accept}`"
-        name="selectFileIframe"
-        @message="handleMessage"
-    />
+    <view></view>
 </template>
 
 <script setup>
-import {ref, getCurrentInstance} from "vue";
-import {onLoad, onReady,onUnload} from '@dcloudio/uni-app'
-
-//初始变量
-const instance = getCurrentInstance().proxy
-const webViewRef = ref(null)
-let wv; //计划创建的webview
-
-const webViewStyle = ref({
-    height: '1px',
-    width: '1px',
-    top: '300px',
-})
-
-const props = defineProps({
-    accept: {
-        type: String,
-        default: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel,application/pdf,.doc,.docx,application/msword'
-    }
-});
+const lemonjkFileSelect = uni.requireNativePlugin('lemonjk-FileSelect');
+//插件地址: https://ext.dcloud.net.cn/plugin?id=14059
 
 //事件
 const emit = defineEmits(['change'])
 
-onLoad(() => {
-    // #ifdef H5
-    window.addEventListener('message', handleMessage);
-    // #endif
-})
-
-//渲染完成
-onReady(() => {
-    // #ifdef APP-PLUS
-    initWebview()
-    // #endif
-})
-
-//初始化webview
-const initWebview = () => {
-    return new Promise((resolve) => {
-        let currentWebview = instance.$scope.$getAppWebview()
-        //如果是页面初始化调用时,需要延时一下
-        setTimeout(() => {
-            wv = currentWebview.children()[0]
-            //ios 禁用缓存,测试生效!!
-            let cache1 = plus.ios.newObject('NSURLCache');
-            let cache = plus.ios.invoke(cache1, 'sharedURLCache');
-            plus.ios.invoke(cache, 'removeAllCachedResponses');
-            plus.ios.invoke(cache, 'setDiskCapacity:', 0);
-            plus.ios.invoke(cache, 'setMemoryCapacity:', 0);
-            //安卓端缓存清理。
-            plus.cache.clear();
-            resolve(true)
-        }, 1000);
-    })
-}
-
-const handleMessage = (event) => {
-    let msg = {};
-    // #ifdef H5
-    if (event.data && event.data.data && event.data.data.arg) {
-        msg = event.data.data.arg
-    }
-    // #endif
-    // #ifdef APP-PLUS
-    msg = event.detail.data[0]
-    // #endif
-    console.log('event.detail', msg)
-    if (msg.source === 'file-web') {
-        if (msg.type === "selectFileChange") {
-            selectFileChange(msg.data)
-        }
-    }
-}
-
-onUnload(()=>{
-    // #ifdef H5
-    window.removeEventListener('message', handleMessage);
-    // #endif
-})
-
 //选择文件
 const selectFile = () => {
-    // #ifdef H5
-    window.frames["selectFileIframe"].postMessage({
-        type: 'selectFile',
-        source: 'file-app',
-        data: {}
-    });
-    // #endif
     // #ifdef APP-PLUS
-    wv.evalJS(`selectFile()`)
+    selectFileApi()
     // #endif
 }
 
-//文件选择完毕
-const selectFileChange = (files) => {
-    if (files && files.length > 0) {
-        let file = files[0];
-        const reader = new FileReader();
-        reader.readAsDataURL(file);
-        //监听文件读取结束后事件
-        reader.onloadend = function (e) {
-            //e.target.result就是最后的路径地址
-            emit('change', {
-                file: file,
-                path: e.target.result
+//手机端选择文件
+const selectFileApi = () => {
+    lemonjkFileSelect.showPicker({
+        mimeType: "*/*",
+        utisType: "public.data"
+    }, (res) => {
+        const code = res.code.toString()
+        if(code === '0') {
+            // [{filePath,fileName,fileExtension,fileSize},...]
+            emit('change', res.files ?? [])
+        } else if(code === '1001'){
+            // #ifdef APP-ANDROID
+            // (仅安卓)当错误码为1001,即未授权文件读取权限,可以提示用户未打开读取文件权限,并跳转设置页
+            uni.showModal({
+                title: "需要文件访问权限",
+                content: "您还未授权本应用读取文件。为保证您可以正常上传文件,请在权限设置页面打开文件访问权限(不同手机厂商表述可能略有差异)请根据自己手机品牌设置",
+                confirmText: "去授权",
+                cancelText: "取消",
+                success(e) {
+                    if(e.confirm){
+                        // 跳转到应用设置页
+                        lemonjkFileSelect.gotoSetting();
+                    }
+                }
+            })
+            // #endif
+            // #ifdef APP-IOS
+            uni.showModal({
+                title: "需要文件访问权限",
+                content: "您还未授权本应用读取文件。为保证您可以正常上传文件,请在设置页面打开文件访问权限",
+            })
+            // #endif
+        } else if(code === '-1'){
+            uni.showModal({
+                title: "错误",
+                content: "选择文件失败 -1"
+            })
+        } else if(code === '1002'){
+            uni.showModal({
+                title: "错误",
+                content: "选择文件失败,文件不存在了"
+            })
+        } else if(code === '1005'){
+            uni.showModal({
+                title: "错误",
+                content: "文件选取出错"
             })
         }
-    }
+    })
 }
 
-//到处函数
+//处函数
 defineExpose({
     selectFile
 })
 </script>
-
-<style scoped lang="scss">
-@import "./style.scss";
-</style>

+ 0 - 0
components/select-file/style.scss


+ 18 - 2
manifest.json

@@ -49,14 +49,14 @@
                     "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
                     "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
                     "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
                     "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
                     "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
                     "<uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\"/>",
                     "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
                     "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
-                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
-                    "<uses-permission android:name=\"com.huawei.android.launcher.permission.CHANGE_BADGE\"/>"
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
                 ],
                 "minSdkVersion" : 24,
                 "abiFilters" : [ "armeabi-v7a", "arm64-v8a" ],
@@ -117,6 +117,22 @@
                 "useOriginalMsgbox" : false,
                 "androidStyle" : "common"
             }
+        },
+        "nativePlugins" : {
+            "lemonjk-FileSelect" : {
+                "__plugin_info__" : {
+                    "name" : "APP文件选择(支持安卓13和苹果)",
+                    "description" : "支持双端的APP文件选取插件",
+                    "platforms" : "Android,iOS",
+                    "url" : "https://ext.dcloud.net.cn/plugin?id=14059",
+                    "android_package_name" : "uni.UNIA0B807E",
+                    "ios_bundle_id" : "com.hczcapp.hczc",
+                    "isCloud" : true,
+                    "bought" : 1,
+                    "pid" : "14059",
+                    "parameters" : {}
+                }
+            }
         }
     },
     /* SDK配置 */

+ 5 - 4
pages/data-fill/fileUp.vue

@@ -85,11 +85,12 @@ const selectImageTap = () => {
 //选择文件上传
 const selectFileRef = ref(null)
 const selectFileTap = () => {
-    selectFileRef.value.selectFile()
+    selectFileRef.value?.selectFile()
 }
 //文件选择完成
-const selectFileChange = ({file, path}) => {
-    if(file) {
+const selectFileChange = (files) => {
+    console.log(files)
+    /*if(file) {
         uni.showLoading({title: '上传文件中...', mask: true})
         uni.uploadFile({
             url: getUploadApi() + 'blade-manager/exceltab/add-buss-file',
@@ -107,7 +108,7 @@ const selectFileChange = ({file, path}) => {
                 uni.hideLoading()
             }
         });
-    }
+    }*/
 }
 
 //处理文件上传

+ 18 - 0
uni_modules/lemon-filePicker/changelog.md

@@ -0,0 +1,18 @@
+## 1.6(2023-09-06)
+1.修复 安卓文件选择bug修复
+## 1.5(2023-08-09)
+1.新增初始目录设置,可以直接打开指定的目录(安卓)
+2.新增返回的字段:文件大小(安卓)、文件后缀名(安卓)
+## 1.4(2023-08-07)
+1.新增ios端返回文件名称
+2.bugfix
+3.说明文档更新
+## 1.3(2023-07-14)
+1.新增文件类型过滤,可以自定义mime类型
+2.新增自动权限检查,提示用户启用文件读写权限,未开启权限时支持自动跳转到App权限设置页
+## 1.2(2023-07-10)
+新增 提供对U盘文件选取支持,可以选取插在手机上的U盘内的文件(安卓)
+## 1.0(2023-6-11)
+
+* 新增 UTS插件,支持 ios和安卓双端
+

+ 68 - 0
uni_modules/lemon-filePicker/package.json

@@ -0,0 +1,68 @@
+{
+    "id": "lemon-filePicker",
+    "displayName": "安卓iOS 双端APP文件选择",
+    "version": "1.6",
+    "description": "支持IOS 、安卓 双平台的APP文件选取插件",
+    "keywords": [
+        "文件选择",
+        "APP",
+        "安卓",
+        "ios"
+    ],
+    "repository": "",
+    "engines": {
+        "HBuilderX": "^3.8.7"
+    },
+    "dcloudext": {
+        "type": "uts"
+    },
+    "uni_modules": {
+        "dependencies": [],
+        "encrypt": [],
+        "platforms": {
+            "cloud": {
+                "tcb": "y",
+                "aliyun": "y"
+            },
+            "client": {
+                "Vue": {
+                    "vue2": "y",
+                    "vue3": "y"
+                },
+                "App": {
+                    "app-android": "y",
+                    "app-ios": "y"
+                },
+                "H5-mobile": {
+                    "Safari": "n",
+                    "Android Browser": "n",
+                    "微信浏览器(Android)": "n",
+                    "QQ浏览器(Android)": "n"
+                },
+                "H5-pc": {
+                    "Chrome": "n",
+                    "IE": "n",
+                    "Edge": "n",
+                    "Firefox": "n",
+                    "Safari": "n"
+                },
+                "小程序": {
+                    "微信": "n",
+                    "阿里": "n",
+                    "百度": "n",
+                    "字节跳动": "n",
+                    "QQ": "n",
+                    "钉钉": "n",
+                    "快手": "n",
+                    "飞书": "n",
+                    "京东": "n"
+                },
+                "快应用": {
+                    "华为": "n",
+                    "联盟": "n"
+                }
+            }
+        }
+    },
+    "name": "安卓iOS 双端APP文件选择"
+}

+ 117 - 0
uni_modules/lemon-filePicker/readme.md

@@ -0,0 +1,117 @@
+# lemon-filePicker
+
+
+# 一、使用
+### 1.在页面引入
+```js
+1.重要(安卓):使用前先勾选文件读取权限,具体步骤:打开manifest.json,选择App权限配置,在Android权限配置中勾选<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+2.若HBuilderX中没有uts编译运行插件,在第一次运行时会自动下载.
+3.如果使用出错,请优先检查HBuilderX是否成功安装了uts编译运行插件,并检查HBuilderX是否更新到了最新版
+4.本插件已做权限检查(安卓),支持跳转到APP权限设置页
+5.支持设置Mime类型(安卓),实现限制文件类型选取
+6.支持访问指定目录(安卓),方便用户在指定目录快速选取
+import fileSelect from "@/uni_modules/lemon-filePicker"
+```
+### 2.唤起文件选择
+```js
+	fileSelect({
+		scope:"/Download",  //具体配置说明见下方 【fileSelect可配置参数说明】
+		permission: false,
+		//实践:
+		//1.用户点击选择文件,调用本选取文件插件,
+		//2.插件会自动检查是否有文件读取权限(没有会弹窗让用户选择)
+		//3.已有权限(或用户点击了同意),直接弹出文件选择
+		//4.没有权限(或用户点击了拒绝),配置了permission:true,会自动跳转到当前APP的权限设置页面,如果需要更好的用户体验(跳转前给用户一个帮助说明或图文操作步骤),请配置permission:false,并在fail回调中处理提示用户的逻辑,用户确认后再次调用本插件,这次请设置permission:true
+		mimetype: "*/*",
+		success(res) {
+			console.log(res);	
+			//返回数据格式说明见下方 【fileSelect返回值说明】				
+			uni.getSystemInfo({
+				success(info) {
+					if (info.osName == 'ios') {
+						console.log("我是ios")
+						// 由于ios文件沙盒机制,需要使用uni.downloadFile下载后,获取到的文件临时路径(_doc/xxx)后使用该路径去上传
+						// 或使用原生插件版文件选择,免此步骤:  https://ext.dcloud.net.cn/plugin?id=14059
+						uni.downloadFile({
+							url: res.filePath,
+							success(e) {
+								console.log(e);
+								// ios请使用该路径(e.tempFilePath)
+								
+							}
+						})
+					} else {
+						console.log("我是安卓")
+						//安卓可以直接使用  res.filePath
+					}
+				}
+			})
+		},
+		fail(err) {
+			console.log(err);
+			// err.code=1001  未授权文件读取权限,可以提示用户未打开读取文件权限(仅安卓)
+			if(err.code==1001){
+				uni.showModal({
+					title:"需要文件访问权限",
+					content:"您还未授权本应用读取文件。为保证您可以正常上传文件,请在权限设置页面打开文件访问权限(不同手机厂商表述可能略有差异)请根据自己手机品牌设置",
+					confirmText:"去授权",
+					cancelText:"算了",
+					success(e) {
+						if(e.confirm){
+							fileSelect({permission: true})
+						}
+					}
+				})
+			}
+		}
+	})
+```
+| fileSelect可配置参数说明 |  |  |  |
+| --- | --- | --- | --- |
+| 属性 | 类型 | 说明 | 兼容性 |
+| scope | string | 【可选】访问指定目录,不需要则不要声明该属性,默认显示顶级目录<br />例:<br />- /DCIM/Camera 相机 <br />-  /Download 下载    <br /> | 仅安卓 (部分目录由于安全策略无法访问,如"Android/data",请自行测试,可以参考自己手机的目录进行设置) |
+| permission | Boolean | 【可选】当读取文件的权限被用户拒绝,是否自动跳转到当前APP的权限设置页面,默认为false(不跳转) | 仅安卓 |
+| mimetype | string | 【必填】限制选取的文件类型,不限制需设为"*/*",更多类型请参照Mime类型对照表,暂时仅支持设置单个类型<br />例: <br />-  image/*    (图片) <br />-  text/plain  (文本)<br />- application/vnd.openxmlformats-officedocument.wordprocessingml.document (word)<br /> | 仅安卓 |
+
+
+| fileSelect返回值说明 |  |  |  |
+| --- | --- | --- | --- |
+| 属性 | 类型 | 说明 | 兼容性 |
+| code | string | 状态码:<br />- 0 成功<br />- 1001 未授权文件读取权限(仅安卓)<br />- 1002 文件不存在(仅安卓)<br />- 1004 用户取消了选择(仅ios)<br /> | <br /> |
+| filePath | string | 选取的文件的绝对路径,可以直接提供给uniapp的上传等api使用(使用该路径时,请确认自己的项目有读取访问文件的权限) |  |
+| fileName | string | 选取的文件的名称 |  |
+| fileSize | string | 选取的文件的大小(单位:字节) |  |
+| fileType | string | 选取的文件的后缀名 |  |
+| errMsg | string | 选择完成后的状态信息  |  |
+| detail | string | 具体的文字说明 |  |
+
+# 兼容性说明
+### 目前仅测试了部分真机(安卓13.0 、ios13),其他系统版本兼容性未知,请自行测试.
+### 由于安卓 API33 (>=Android13)系统隐私安全管控更为严格,废弃了常规的读取和写入权限,所以暂时只支持 targetSdkVersion<=32,请检查manifest.json中配置项targetSdkVersion
+# 问题反馈与收集
+```js
+感谢使用,本插件已停止维护,若遇到问题请加qq群466852060讨论交流
+本uts插件为开放源代码插件,可自行在 uni_modules->lemon-filePicker->utssdk 直接修改对应平台的源代码
+
+# 已上线[原生插件]版文件选取,同样支持安卓和ios双端,支持更多功能
+前往使用:https://ext.dcloud.net.cn/plugin?id=14059(请根据自己实际真机测试选择使用uts版或原生插件版)
+
+
+目前收集的问题(Q&A):
+1.文件查找失败:'@/uni_modules/lemon-filePicker' 
+2.(已解决,ios,应该?)Error: undefined class: UTSSDKModulesLemonFilePickerIndexSwift
+遇到问题2,大概率是你用的window电脑,直接用基本调试基座运行的。
+   一、官方文档说明如下:
+	  运行到ios平台,uts插件编译需要XCode环境,因此在mac电脑安装了XCode工具时支持直接使用标准基座真机运行。 
+	  在windows电脑或者mac电脑没有安装XCode工具时,需要提交云端打包生成自定义基座后才能调用uts插件
+   二、简单来说:
+    1.你是windows电脑?请点击 运行->原生APP-云打包->选择ios(ipa),填写相关苹果证书配置,选择打自定义基座调试包。打包完成后,运行时选择使用自定义基座运行
+	2.你是mac电脑?
+	  2.1 有xcode环境?参考这个[xcode配置](https://uniapp.dcloud.net.cn/tutorial/run/uts-development-ios.html)配置后,直接标准基座运行
+	  2.2 没有xcode环境?安装完成后参考2.1,不想安装参考1
+
+3.(已解决,安卓)u盘文件无法选择
+4.(已解决,安卓)文件类型筛选
+5.(已解决,安卓)权限检查
+6. HbuilderX编辑器报错 xxx与uts相关的模块未找到,请重新下载最新的HbuilderX再运行
+```

+ 285 - 0
uni_modules/lemon-filePicker/utssdk/app-android/index.uts

@@ -0,0 +1,285 @@
+// index.uts
+
+// 引用android api
+import { UTSAndroid } from "io.dcloud.uts";
+import ActivityCompat from "androidx.core.app.ActivityCompat";
+import { Context, Intent, ContentUris, ContentResolver } from "android.content";
+import { Settings } from "android.provider";
+import Uri from "android.net.Uri";
+import FileUtils from "android.os.FileUtils";
+import { DocumentsContract, MediaStore } from "android.provider";
+import { File, FileOutputStream } from "java.io";
+import Activity from "android.app.Activity";
+import Manifest from "android.Manifest";
+import Cursor from "android.database.Cursor";
+import { Build, Environment } from "android.os";
+ 
+    
+type InfoOptions = {
+	scope?:string,
+	permission ?: boolean,
+	mimetype?:string,
+	success ?: (res : object) => void
+	fail ?: (res : object) => void
+	complete ?: (res : object) => void
+}
+export default function fileSelect(options : InfoOptions) {
+	UTSAndroid.onAppActivityRequestPermissionsResult((requestCode : number,
+		permissions : MutableList<string>,
+		grantResults : MutableList<number>) => {
+		UTSAndroid.offAppActivityRequestPermissionsResult();
+		if ((requestCode !== 1001) && (grantResults[0] === -1)) {
+			options.fail?.({
+				code: "1001",
+				errMsg: 'fileselect:fail',
+				detail:"未授权文件读取权限"
+			})
+			const permission=options.permission;
+			if(permission!=null&&permission){
+				const intent = new Intent();
+				intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+				const uri = Uri.fromParts("package", UTSAndroid.getUniActivity()?.getPackageName(), "");
+				intent.setData(uri);
+				UTSAndroid.getUniActivity()?.startActivity(intent);
+			}
+			
+		} else {
+			fileSelectStart(options);
+
+		}
+	});
+
+	// 请求权限 
+	ActivityCompat.requestPermissions(UTSAndroid.getUniActivity()!, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), 1001)
+
+}
+ function fileSelectStart(options : InfoOptions) {
+	const FILE_SELECT_REQUEST_CODE = 110
+	const context = UTSAndroid.getAppContext();
+	if (context != null) {
+		if(options.scope!=null){ 
+			let strPath=options.scope
+			strPath=strPath?.replaceAll("/","%2F")  
+			console.log(strPath);
+			let uri1:Uri = Uri.parse("content://com.android.externalstorage.documents/document/primary%3A"+strPath);
+			let intent1:Intent = new Intent(Intent.ACTION_GET_CONTENT);
+			intent1.addCategory(Intent.CATEGORY_OPENABLE);  
+			if(options.mimetype!=null){ 
+				intent1.setType(options.mimetype);
+			}else{
+				intent1.setType("*/*");
+			}
+			intent1.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri1);
+			UTSAndroid.getUniActivity()?.startActivityForResult(intent1, FILE_SELECT_REQUEST_CODE);
+			
+		}else{
+			const intent = new Intent(Intent.ACTION_GET_CONTENT);
+			intent.setType("*/*");
+			intent.addCategory(Intent.CATEGORY_OPENABLE);
+			if(options.mimetype!=null){
+				intent.setType(options.mimetype);
+			}
+			UTSAndroid.getUniActivity()?.startActivityForResult(intent, FILE_SELECT_REQUEST_CODE)
+		}
+		
+		UTSAndroid.onAppActivityResult((requestCode : Int, resultCode : Int, data ?: Intent) => {
+			// let eventName = "onAppActivityResult  -  requestCode:" + requestCode + " -resultCode:"+resultCode + " -data:"+JSON.stringify(data);
+			UTSAndroid.offAppActivityResult();
+			if (requestCode == FILE_SELECT_REQUEST_CODE && resultCode == Activity.RESULT_OK && data != null) {
+				const fileUri = data.getData();
+ 
+				if (fileUri != null) {
+					// console.log('文件选择器:【文件相对路径】', fileUri.getPath());
+					let path = getRealPathFromURI(context, fileUri)
+					const file = new File(path)
+					if (file.exists()) {
+						let upLoadFilePath = file.toString();
+						let upLoadFileName = file.getName();
+						let fileSize = file.length();
+						const extIdx= upLoadFilePath.lastIndexOf(".");
+						let fileType =extIdx!=-1?upLoadFilePath.substring(extIdx+1):""
+						const res = {
+							code: "0",
+							filePath: upLoadFilePath,
+							fileName: upLoadFileName,
+							fileSize:fileSize,
+							fileType:fileType,
+							errMsg: 'fileselect:ok',
+							detail:"文件读取成功"
+						}
+						options.success?.(res)
+						options.complete?.(res)
+					} else {
+						const res2 = {
+							code: "1002",
+							errMsg: 'fileselect:fail',
+							detail:"文件不存在"
+						}
+						options.fail?.(res2)
+						options.complete?.(res2)
+					}
+
+				}
+			}
+		});
+	} else {
+		const res3 = {
+			code: "1002",
+			errMsg: 'fileselect:fail',
+			detail:"文件不存在"
+		}
+		options.fail?.(res3)
+		options.complete?.(res3)
+	}
+
+}
+
+function getRealPathFromURI(context : Context, uri : Uri) : string {
+	const isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+	//4.4以下的版本:不支持
+	//大于4.4	
+	// DocumentProvider 
+	if (isKitKat) {
+		if (DocumentsContract.isDocumentUri(context, uri)) {
+			if (isExternalStorageDocument(uri)) {
+				const docId : string = DocumentsContract.getDocumentId(uri);
+				const split : string[] = docId.split(":");
+				console.log(docId, split);
+				if ("primary".equals(split[0])) {
+					return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + split[1];
+				} else {
+					return "/storage/" + split[0] + "/" + split[1]
+				}
+			}
+			// DownloadsProvider
+			else if (isDownloadsDocument(uri)) {
+				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+					return saveFileFromUri(context, uri)
+				}
+				const downloadPath: string = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
+				const fileName:string = getFileName(context, uri);
+				return downloadPath+"/"+fileName
+			}
+			// MediaProvider 
+			else if (isMediaDocument(uri)) {
+				let docId : string = DocumentsContract.getDocumentId(uri);
+				let split : string[] = docId.split(":");
+				let type : string = split[0];
+				let contentUri : Uri | null = null;
+
+				if ("image".equals(type)) {
+					contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+				} else if ("video".equals(type)) {
+					contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+				} else if ("audio".equals(type)) {
+					contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+				}
+				else if ("document".equals(type)) {
+					// 安卓系统版本大于等于10
+					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+						return saveFileFromUri(context, uri)
+					}
+					return ""
+				}
+				const selection : string = "_id=?";
+				const selectionArgs : String[] = [split[1]];
+				if (contentUri != null) {
+					return getDataColumn(context, contentUri, selection, selectionArgs);
+				}
+			}
+		}
+		//其他 content
+		else if ("content".equals(uri.getScheme())) {
+			return getDataColumn(context, uri, null, null);
+
+		}
+		//其他 file
+		else if ("file".equals(uri.getScheme())) {
+			return uri.getPath() as string;
+		}
+	}
+	return ""
+
+}
+function isExternalStorageDocument(uri : Uri) : boolean {
+	return "com.android.externalstorage.documents".equals(uri.getAuthority());
+}
+
+function isDownloadsDocument(uri : Uri) : boolean {
+	return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+}
+function isMediaDocument(uri : Uri) : boolean {
+	return "com.android.providers.media.documents".equals(uri.getAuthority());
+}
+
+function getDataColumn(context : Context, uri : Uri, selection : string | null, selectionArgs : String[] | null) : string {
+	let column = "_data";
+	let projection = arrayOf<string>(column)
+	let cursor : Cursor | null = null;
+	const _selectionArgs = selectionArgs != null ? selectionArgs.toTypedArray() : null
+	
+	try {
+
+		cursor = context.getContentResolver().query(uri, projection, selection, _selectionArgs, null);
+		
+		if (cursor != null && cursor.moveToFirst()) {
+			const column_index = cursor.getColumnIndexOrThrow(column);
+			return cursor.getString(column_index);
+		}
+	} catch (e) {
+		console.log(e);
+	} finally {
+		if (cursor != null) { 
+			cursor.close();
+		}
+	}
+	return "";
+}
+function getFileName(context : Context, uri : Uri) : string {
+	let projection = arrayOf(MediaStore.Images.ImageColumns.DISPLAY_NAME)
+	let cursor = context.getContentResolver().query(uri, projection, null, null, null)
+	try {
+		if (cursor != null && cursor.moveToFirst()) {
+
+			let name_col_index = cursor.getColumnIndex(projection[0])
+
+			return cursor.getString(name_col_index)
+		}
+	} catch (e) {
+		console.log(e);
+
+	} finally {
+		cursor?.close()
+	}
+	return ""
+}
+
+function saveFileFromUri(context : Context, uri : Uri) : string {
+	let file : File;
+	const contentResolver : ContentResolver = context.getContentResolver();
+	const cursor : Cursor | null = contentResolver.query(uri, null, null, null, null);
+	if (cursor != null && cursor.moveToFirst()) {
+		const displayName = getFileName(context, uri)
+		try {
+
+			const is = contentResolver.openInputStream(uri);
+			if (is != null) {
+				const file1 : File = new File(context.getExternalCacheDir()?.getAbsolutePath() + "/" + System.currentTimeMillis());
+				if (!file1.exists()) {
+					file1.mkdir();
+				}
+				const cache : File = new File(file1.getPath(), displayName);
+				const fos = new FileOutputStream(cache);
+				FileUtils.copy(is, fos);
+				file = cache;
+				fos.close();
+				is.close();
+				return file.getAbsolutePath();
+			}
+		} catch (e) {
+			console.log(e);
+		}
+
+	}
+	return ""
+}

+ 71 - 0
uni_modules/lemon-filePicker/utssdk/app-ios/index.uts

@@ -0,0 +1,71 @@
+// 引用 iOS 原生平台 api
+import {  UIDocumentPickerViewController, UIDocumentPickerMode, UIDocumentPickerDelegate, UIViewController } from "UIKit";
+import { DispatchQueue } from 'Dispatch';
+import { UTSiOS } from "DCloudUTSFoundation"
+import { URL ,FileManager } from 'Foundation'
+/**
+ * 定义 接口参数
+ */
+type InfoOptions = {
+	success ?: (res : UTSJSONObject) => void;
+	fail ?: (res : UTSJSONObject) => void;
+	complete ?: (res : UTSJSONObject) => void;
+};
+class DocumentPicker extends UIViewController implements UIDocumentPickerDelegate {
+	docPicker! : UIDocumentPickerViewController
+	infoOptions ?: InfoOptions
+
+	documentPicker(controller : UIDocumentPickerViewController, @argumentLabel("didPickDocumentsAt") urls : URL[]) {
+		const fileName=urls[0].lastPathComponent
+		try {
+			const res = {
+				code: "0",
+				filePath:urls[0].absoluteString,
+				fileName: fileName,
+				errMsg: 'fileselect:ok',
+				detail: "文件读取成功"
+			}
+			
+			if (this.infoOptions != null) {
+				this.infoOptions?.success?.(res)
+				this.infoOptions?.complete?.(res)
+			}
+		}catch (e) {
+			console.log(e.message)
+			const res_fail = {
+				code: "1002",
+				errMsg: 'fileselect:fail',
+				detail: "文件不存在"
+			}
+			this.infoOptions?.fail?.(res_fail)
+			this.infoOptions?.complete?.(res_fail)
+		}
+	}
+	documentPickerWasCancelled(controller : UIDocumentPickerViewController) {
+		const res = {
+			code: "1004",
+			errMsg: 'fileselect:fail',
+			detail: "用户取消了选择"
+		}
+		this.infoOptions?.fail?.(res)
+		this.infoOptions?.complete?.(res)
+	}
+	fileSelect(options : InfoOptions) {
+
+		this.infoOptions = options
+		DispatchQueue.main.async(execute = () : void => {
+			const types = ["public.data"]
+			if (this.docPicker == null) {
+				this.docPicker = new UIDocumentPickerViewController(documentTypes = types, in = UIDocumentPickerMode.import)
+				this.docPicker.delegate = this
+			}
+			UTSiOS.getCurrentViewController().present(this.docPicker, animated = true)
+		})
+	}
+}
+const docPicker : DocumentPicker = new DocumentPicker();
+
+export default function fileSelect(options : InfoOptions) {
+
+	docPicker.fileSelect(options)
+}