This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

+16
View File
@@ -0,0 +1,16 @@
## 1.0.82025-08-17
* 兼容支持鸿蒙 Next
## 1.0.42025-01-12
* 支持微信小程序
## 1.0.32024-09-12
* 安卓更新了相关bug,另外使用方法改变以了有些方法类型变以了,更新时请注意比对demo方法.
## 1.0.22024-08-14
* 新增属性超过指定文件大小,会被标注,并且三端会返回文件大小。
## 1.0.12024-05-24
* 本次适配了IOS 11+(含),现在你可以在ios,安卓,web下选择文件上传了哦。
* 请认真看说明文档使用,使用复杂了点,由于安卓,ios隐私等限制没办法抹平使用差异,请看仔细使用。如果不会用,请使用我tmui4.0x中的x-upload-file来进行文件上传。
## 1.0.02024-04-13
* 原生文件选择器,支持安卓(不需要权限,也不需要打基座,标准基座即可)
* web原生支持
* ios待适配
* tmui4.0种子用户,可免费赠送源码,无需购买
+100
View File
@@ -0,0 +1,100 @@
{
"id": "x-file-s",
"displayName": "tm-ui-4.0 安卓 IOS WEB 鸿蒙 文件选择器 通用无权限使用",
"version": "1.0.8",
"description": "安卓端不需要权限,web端,ios三端规格统一,抹平差异。",
"keywords": [
"安卓,web文件选择器,ios,鸿蒙"
],
"repository": "",
"engines": {
"HBuilderX": "^3.6.8",
"uni-app": "",
"uni-app-x": "^4.75"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "99.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"darkmode": "x",
"i18n": "x",
"widescreen": "x"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "√",
"aliyun": "√",
"alipay": "√"
},
"client": {
"uni-app": {
"vue": {
"vue2": "-",
"vue3": "-"
},
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"vue": "-",
"nvue": "-",
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-",
"alipay": "-",
"toutiao": "-",
"baidu": "-",
"kuaishou": "-",
"jd": "-",
"harmony": "-",
"qq": "-",
"lark": "-"
},
"quickapp": {
"huawei": "-",
"union": "-"
}
},
"uni-app-x": {
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"android": {
"extVersion": "",
"minVersion": "21"
},
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√"
}
}
}
}
}
}
+187
View File
@@ -0,0 +1,187 @@
# x-file-s
### 开发文档
[TMUI4.0文档](https://xui.tmui.design/)
[TMUI4.0组件库](https://ext.dcloud.net.cn/plugin?id=16369)
**如果需要查看效果请[下载TMUI4.0组件库应用demo](https://ext.dcloud.net.cn/plugin?id=16369),导航到原生插件栏目体验。**
### 功能特色
** 本插件是API接口插件非组件,插件截图是我的TMUI4.0 x-upload-file基于此api接口封装的ui组件 **
- 支持安卓,无权限操作
- 支持web选择文件上传
- 支持多选,单选
- 支持微信
### 功能说明
**请仔细看下面的使用方法,ios和web,安卓有区别注意条件编译进上传上传**
与官方 新 出的文件选择最大不同的是:
1. 选择的文件不会有真实的文件,还要在上传时再执行内部 copy,因此理论上你选择 100 个文件上传,都是单个循环上传,不会造成崩溃
除非你单个文件就非常大。(官方的是限制数量 100,默认选择后就给你一起复制到缓存了,而我是按需复制缓存文件。)
2. 后缀不一样,可以任意标签的 miniType 格式如 application/msword等。
- 安卓端无需权限,返回系统的文件列表,但不包含真实文件地址。如果需要操作上传读取等,需要配置copyFileToPath方法,生成一个你自己的文件缓存目录。把文件复制到此位置。
- 如果不要用了,要自己通过官方uni.getFileSystemManager来管理和清空缓存文件夹。不然应用随意使用的推移会增大。
- web端直接选择上传没有上述问题
- ios已支持从系统文件 管理器选择任意文件上传,它同安卓一样会在选择的时候copy一份文件到应用专属缓存文件中(不用担心变大,会自动清除),选择的文件只能当即临时上传
时间长了会自动删除。返回的文件路径需要自行uni.env.CACHE_PATH + "/" + item.realFilePath组合路径进行上传
- 如果你不会使用,建议使用我的tmui4.0组件中的x-upload-file,它是基于此api接口封装的上传组件,啥都帮你配置好了,你只要提供个服务器的上传地址。
### 兼容性
| Harmony | IOS | Android | WEB | 小程序 |
| --- | --- | --- | --- | --- |
| 支持 | 支持 | 支持 | 支持 | 支持 |
### 说明
这是tmui4.0|XUI的原生插件附赠插件。种子用户可免于购买。
非种子用户:
普通授权:99元
源码授权:199元
### 使用
如果你需要32位系统和模拟器x86上运行,需要自行配置打包cpu 支持类型,配置abis
需要自定义基座运行,且安卓4.4+(含4.4)以上支持
```ts
// 导入
import {xFileSystem,fileListType} from "@/uni_modules/x-file-s"
/**
* 新建文件管理对象,
* typeFilter:string|null 文件过滤器例:"*/*",或者所有图片 "image/*"
* tempmultiple:boolean|null 是否多选,默认是
* maxSize:number|null 限制的文件大小,不是禁止用户选中,是返回的列表会标stauts=4,表示超过了大小。
*/
this.xfile = new xFileSystem(null,null)
// 打开文件夹选择文件
this.xfile!.openDocument((list:any)=>{
// 安卓是 list:fileListType[]是选中的文件列表,但这个文件列表是虚拟的,并非真实的文件地址。
// web端list:fileListType[] 可以通过item.file访问文件对象即File
// ios端是string
let list = [] as fileListType[];
// #ifdef APP-IOS
let templist = JSON.parseArray<string>(listtest)
for(let i=0;i<templist.length;i++){
let item = JSON.parse(templist[i])! as fileListType;
list.push(item)
}
// #endif
// #ifndef APP-IOS
list = listtest as fileListType[]
// #endif
})
// 读取文件,为了抹平差异,web,安卓,ios编译在读取或者上传前调用以下函数获得上传的上传地址
// 上传前需要一个临时存在的路径地址,这样文件会复制到你提供的目录缓存下。因此要选创建一个临时目录
fileIsOnlyInSys( dir:string):Promise<string>{
// #ifdef WEB
return Promise.resolve("")
// #endif
//默认创建一个临时的 tmui4temp 目录
let baseNamePath = dir==null?'tmui4temp':dir
baseNamePath = uni.env.CACHE_PATH + "/" + baseNamePath
const fileManager = uni.getFileSystemManager()
return new Promise((res,rej)=>{
fileManager.readdir({
dirPath:baseNamePath,
success(result:ReadDirSuccessResult){
if(result==null){
fileManager.mkdir({
dirPath:baseNamePath,
recursive:true,
success(result:FileManagerSuccessResult){
res(baseNamePath)
},
fail(er:UniError){
console.log('创建目录失败',er)
rej("")
}
} as MkDirOptions)
}else{
// 这个是目录路径
res(baseNamePath)
}
},
fail(ere:UniError){
fileManager.mkdir({
dirPath:baseNamePath,
recursive:true,
success(result:FileManagerSuccessResult){
res(baseNamePath)
},
fail(er:UniError){
console.log('创建目录失败',er)
rej("")
}
} as MkDirOptions)
}
} as ReadDirOptions)
})
}
//调用上述方法,确保临时目录存在后通过Promise回调来复制文件路径
// web,安卓如下执行读取真实路径给uploadfile来上传
this.fileIsOnlyInSys('tmui4temp')
.then((basedir:string)=>{
_this.xfile!.copyFileToPath(item.id,basedir)
.then((oreal:fileListType|null)=>{
// oreal 这个对象包含了真实的上传地址
// 在这里面你可以读取,上传文件了。
})
})
// ios端不需要执行上面的步骤。
// 直接就是前面的项目fileListType中的 uni.env.CACHE_PATH + "/" + item.realFilePath 组合路径进行上传
```
## 类型参考
```ts
// api内部类型。
type xFileSNameTYpe = {
name:string,
type:string,
uri:Uri,
id:string,
realFilePath:string,
cacheFilePath:string,
size:number,
status:number
}
// 你外部使用的文件对象类型
export type xFileSListType = {
name:string,
type:string,
id:string,
/** web平台文件对象 */
file?:any,
/** 个人用户自定数据 */
request?:any,
/** 安卓机/ios机下面的真实sd路径 */
realFilePath:string,
/** uniapp应用协议文件路径,可用uni.getFileSystemManager进行删除或者查询到 */
cacheFilePath:string,
/** 文件大小 */
size?:number
/**
* 0等待,1失败,2成功,3上传中,4超过大小。
*/
status?:0|1|2|3|4
}
```
@@ -0,0 +1,3 @@
{
"minSdkVersion": "19"
}
@@ -0,0 +1,432 @@
import MediaStore from "android.provider.MediaStore"
import Activity from 'android.app.Activity';
import Bundle from 'android.os.Bundle';
import Intent from 'android.content.Intent';
import Uri from "android.net.Uri"
import Context from 'android.content.Context'
import ContentResolver from 'android.content.ContentResolver'
import InputStream from 'java.io.InputStream'
import FileOutputStream from 'java.io.FileOutputStream'
import File from 'java.io.File'
import ByteArrayOutputStream from 'java.io.ByteArrayOutputStream'
import MimeTypeMap from 'android.webkit.MimeTypeMap'
import OpenableColumns from 'android.provider.OpenableColumns'
import Build from 'android.os.Build'
import ParcelFileDescriptor from 'android.os.ParcelFileDescriptor'
import { xFileSListType,fileListType } from "../interface.uts"
type xFileSNameTYpe = {
name : string,
type : string,
uri : Uri,
id : string,
realFilePath : string,
cacheFilePath : string,
fileSize : number,
status : number
}
type callReadBackFun = (byte : ByteArray) => void
type resumtype = (requestCode : Int, resultCode : Int, data : any) => void
const mimeTypes = new Map<string, string>([
// 图片类型
["jpg", "image/*"],
["jpeg", "image/*"],
["png", "image/*"],
["gif", "image/*"],
["bmp", "image/*"],
["webp", "image/*"],
["tiff", "image/*"],
["svg", "image/*"],
["ico", "image/*"],
// 视频类型
["mp4", "video/*"],
["webm", "video/*"],
["ogv", "video/*"],
["mov", "video/*"],
["avi", "video/*"],
["wmv", "video/*"],
// 音频类型
["mp3", "audio/*"],
["wav", "audio/*"],
["ogg", "audio/*"],
["flac", "audio/*"],
["aac", "audio/*"],
// 文档类型
["pdf", "application/*"],
["doc", "application/*"],
["docx", "application/*"],
["xls", "application/*"],
["xlsx", "application/*"],
["ppt", "application/*"],
["pptx", "application/*"],
["odt", "application/*"],
["ods", "application/*"],
// 文本类型
["txt", "text/plain"],
["html", "text/html"],
["css", "text/css"],
["js", "text/javascript"],
["xml", "text/xml"],
// 其他类型
["zip", "application/*"],
["rar", "application/*"],
["7z", "application/*"],
["json", "application/*"]
]);
function getMimeTypeFromUri(uri : Uri, max : number) : xFileSNameTYpe {
let context = UTSAndroid.getAppContext() as Context
let contentResolver = context.getContentResolver() as ContentResolver;
let mime = MimeTypeMap.getSingleton() as MimeTypeMap;
let gz = mime.getExtensionFromMimeType(contentResolver.getType(uri)) as string | null;
let str = uri.getEncodedPath()
let par = contentResolver.openFileDescriptor(uri, "r") as ParcelFileDescriptor
let filesize = par.getStatSize()
if (gz == null) {
gz = str!.substring(str!.lastIndexOf(".") + 1)
}
let name = str!.substring(0, str!.lastIndexOf("/"))
name = str!.substring(0, str!.lastIndexOf("."))
///安卓10,api 29+需要遵循隐私策略获取名称。
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
let cursor = contentResolver.query(uri, null, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
let nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (nameIndex != -1) {
name = cursor.getString(nameIndex);
}
cursor.close();
}
// 确保关闭游标
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
return {
name,
type: gz!,
uri: uri,
id: Math.floor(Math.random() * 1 * Math.floor(Math.random() * Date.now())).toString().substring(0, 10),
realFilePath: "",
cacheFilePath: "",
fileSize: filesize,
status: filesize > max ? 4 : 0
} as xFileSNameTYpe;
}
function readFileToInputStrem(uri : Uri) : ByteArray {
//读写块的大小。
let sizeBlock = 1024
let context = UTSAndroid.getAppContext() as Context
let cover = context.getContentResolver() as ContentResolver
let inputStream = cover.openInputStream(uri)
let byteArrayOutputStream = new ByteArrayOutputStream();
// 文件的字节数组
let buffer = new ByteArray(sizeBlock.toInt());
let n = inputStream!.read(buffer)
while (-1 != n) {
byteArrayOutputStream.write(buffer, 0, n);
n = inputStream!.read(buffer)
}
return buffer;
}
let filelistAll = [] as xFileSNameTYpe[]
function isInfilelist(file : xFileSNameTYpe) : boolean {
return filelistAll.some((el) : boolean => {
return el.name == file.name && el.type == file.type
})
}
let callFun = (list : fileListType[]) => { }
let nowXFileSystem = null as null|xFileSystem;
const activityResult = (requestCode : Int, resultCode : Int, data ?: Intent)=>{
let jgCode = 1;
console.log(1)
if (data != null && requestCode == jgCode.toInt()) {
let fdata = data! as Intent
let flistdata = fdata.getClipData();
let flistdataOnly = data?.data
//多选
if (flistdata != null) {
let count = flistdata.getItemCount();
for (let i = 0; i < count; i++) {
let item = flistdata.getItemAt(i.toInt())
let uri = item.getUri()
let itemfile = getMimeTypeFromUri(uri, nowXFileSystem!.maxFileSize)
if (!isInfilelist(itemfile)) {
filelistAll.push(itemfile)
}
}
//单选
} else if (flistdataOnly != null) {
let itemfile = getMimeTypeFromUri(flistdataOnly!, nowXFileSystem!.maxFileSize)
if (!isInfilelist(itemfile)) {
filelistAll.push(itemfile)
}
}
let listfile = filelistAll.map((el) : fileListType => {
return {
name: el.name,
type: el.type,
id: el.id,
uri: '',
realFilePath: el.realFilePath,
cacheFilePath: el.cacheFilePath,
status: el.fileSize > nowXFileSystem!.maxFileSize ? 4 : 0,
size: el.fileSize
} as fileListType
});
callFun(listfile)
}
}
/**
* 由于安卓隐私的问题。实际得到的所有文件 其实 都不是真实地址。因此你想要得到真实的地址,你就得复制到你的应用沙盒中。
*/
export class xFileSystem {
mimeTypeFilter : string[] = [] as string[]
filelist = [] as xFileSNameTYpe[]
multiple : boolean = true;
maxFileSize = 30 * 1024 * 1024;
constructor(typeFilter : string[] | null, tempmultiple : boolean | null, maxSize : number | null) {
if (typeFilter != null) {
let filter : string[] = [] as string[];
let tms = typeFilter! as string[]
for (let i = 0; i < tms.length; i++) {
if (mimeTypes.get(tms[i]) != null) {
filter.push(mimeTypes.get(tms[i])!)
}
}
if (filter.length == 0) {
filter.push("*/*")
}
this.mimeTypeFilter = filter
}
if (tempmultiple != null) {
this.multiple = tempmultiple!
}
if (maxSize != null) {
this.maxFileSize = maxSize!
}
nowXFileSystem = this;
UTSAndroid.onAppActivityResult(activityResult);
}
destory() {
UTSAndroid.offAppActivityResult(activityResult)
// filelistAll = [] as xFileSNameTYpe[]
}
openDocument(call : (list : fileListType[]) => void) {
let intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
} else {
intent.setAction(Intent.ACTION_GET_CONTENT);
}
// intent.addCategory(Intent.CATEGORY_OPENABLE);
if (this.mimeTypeFilter.length == 0) {
intent.type = "*/*"
} else {
let key = this.mimeTypeFilter[this.mimeTypeFilter.length - 1]
let ftype = key;
if (ftype != null) {
intent.setType(ftype!);
} else {
intent.setType("*/*");
}
// console.log(this.mimeTypeFilter)
// intent.putExtra(Intent.EXTRA_MIME_TYPES, []);
}
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, this.multiple)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
let _this = this;
callFun = call;
UTSAndroid.getUniActivity()!.startActivityForResult(intent, 1);
}
/**
* 删除一个文件条目
* @param {string} id - 文件id
* @returns {fileListType[]} 最新的文件列表
*/
remove(id : string) : fileListType[] {
let index = -1;
for (let i = 0; i < filelistAll.length; i++) {
let item = filelistAll[i]
if (item.id == id) {
filelistAll.splice(i, 1)
break;
}
}
return filelistAll.map((el) : fileListType => {
return {
name: el.name,
type: el.type,
id: el.id,
uri: '',
realFilePath: el.realFilePath,
cacheFilePath: el.cacheFilePath,
size: el.fileSize,
status: el.status
} as fileListType
});
}
/**
* 返回一个空文件列表数组
*/
clear() : fileListType[] {
filelistAll = [] as xFileSNameTYpe[];
this.filelist = [] as xFileSNameTYpe[];
return [] as fileListType[]
}
/**
* 读取文件
* @description 提供这个函数是因为如果是大文件上传,你可能需要分块上传。由于安卓隐私问题,大文件超过16mb复制回用户区可能会卡或者其它意外事项。
* @param {string} id - 文件id
* @param {number} size - 读写块的大小,每次读取的大小
* @param {Function<byte:ByteArray>} callBack - 读取文件时的回调。
*/
readFile(item : xFileSListType, size : number | null = null, callBack : null | callReadBackFun = null) : ByteArray {
let index = -1;
for (let i = 0; i < filelistAll.length; i++) {
let item2 = filelistAll[i]
if (item.id == item2.id) {
index = i;
break;
}
}
if (index == -1) {
console.error("文件id不存在")
return new ByteArray((0).toInt())
}
let file = filelistAll[index]
// 1024
let sizeBlock = size == null ? 1024 : size!;
let context = UTSAndroid.getAppContext() as Context
let cover = context.getContentResolver() as ContentResolver
let inputStream = cover.openInputStream(file.uri)
let byteArrayOutputStream = new ByteArrayOutputStream();
// 文件的字节数组
let buffer = new ByteArray(sizeBlock.toInt());
let n = inputStream!.read(buffer)
while (-1 != n) {
byteArrayOutputStream.write(buffer, 0, n);
if (callBack != null) {
let tempbuffer = new ByteArray(sizeBlock.toInt());
byteArrayOutputStream.write(tempbuffer, 0, n);
callBack(tempbuffer)!
}
n = inputStream!.read(buffer)
}
let ar = byteArrayOutputStream.toByteArray()
try {
byteArrayOutputStream.flush()
} catch (e) {
byteArrayOutputStream.close()
}
return ar
}
/**
* 获取文件地址
* @description 主要是用来你外部上传用,但记住这个内部会放到临时目录中,因此你的地址也是临时在用户中。
* @returns {Promise<string>} 返回文件地址
*/
copyFileToPath(item : xFileSListType, path : string) : Promise<xFileSListType | null> {
// let index = -1;
// for(let i=0;i<this.filelist.length;i++){
// let item = this.filelist[i]
// if(item.id==id){
// index = i;
// break;
// }
// }
// if(index==-1){
// console.error("文件id不存在")
// uni.showToast({title:"文件id不存在",icon:'none'})
// return Promise.resolve(null)
// }
let file = item
if (file.status == 4) {
console.error("文件大小超了")
// uni.showToast({title:"文件大小超了",icon:'none'})
return Promise.resolve(item)
}
let fileData = this.readFile(item, 1024, null);
let filepath = path + "/" + file.name
let realfilepath = UTSAndroid.convert2AbsFullPath(filepath)
// let outputFile = new File(filepath)
uni.showLoading({ title: '...', mask: true })
return new Promise((res, rej) => {
try {
let outputStream = new FileOutputStream(realfilepath)
outputStream.write(fileData);
outputStream.flush();
uni.hideLoading()
file.realFilePath = realfilepath
file.cacheFilePath = filepath
res({
name: file.name,
type: file.type,
id: file.id,
realFilePath: file.realFilePath,
cacheFilePath: file.cacheFilePath,
status: file.status,
size: file.size,
uri: file.uri
} as xFileSListType)
} catch (e) {
console.error(e)
uni.hideLoading()
uni.showToast({ title: "异常", icon: 'none' })
rej("异常")
}
})
}
}
@@ -0,0 +1,5 @@
{
"dependencies": {
"x_file_s": "./lib/x_file_s.har"
}
}
@@ -0,0 +1,92 @@
import { x_xFileSNameTYpe, x_xFileSystem } from "x_file_s"
import { fileListType, xFileSListType } from "../interface"
let HxFilesSytem : x_xFileSystem | null = null;
export class xFileSystem {
mimeTypeFilter : string[] = []
filelist = [] as fileListType[]
multiple : boolean = true;
maxFileSize = 30 * 1024 * 1024;
constructor(typeFilter : string[] | null, tempmultiple : boolean | null, maxSize : number | null) {
if (typeFilter != null) {
let tms = typeFilter! as string[];
this.mimeTypeFilter = tms;
}
if (tempmultiple != null) {
let tm = tempmultiple as boolean;
this.multiple = tm
}
if (maxSize != null) {
this.maxFileSize = maxSize!
}
if (HxFilesSytem == null) {
HxFilesSytem = new x_xFileSystem(UTSHarmony.getCurrentWindow()!.getUIContext(), this.mimeTypeFilter, this.multiple, this.maxFileSize)
} else {
HxFilesSytem!.clear();
}
}
destory() {
HxFilesSytem = null;
this.filelist = [] as fileListType[]
}
openDocument(callfun : (list : fileListType[]) => void) {
if (HxFilesSytem != null) {
HxFilesSytem!.openDocument((templist : x_xFileSNameTYpe[]) => {
let listfile = templist.map((el) : fileListType => {
return {
name: el.name,
type: el.type,
id: el.id,
uri: el.uri,
realFilePath: el.realFilePath,
cacheFilePath: el.cacheFilePath,
status: el.status,
size: el.fileSize
} as fileListType
});
this.filelist = listfile
callfun(listfile)
})
}
}
/**
* 删除一个文件条目
* @param {string} id - 文件id
* @returns {fileListType[]} 最新的文件列表
*/
remove(id : string) : fileListType[] {
const templist = HxFilesSytem!.remove(id)
let listfile = templist.map((el) : fileListType => {
return {
name: el.name,
type: el.type,
id: el.id,
uri: el.uri,
realFilePath: el.realFilePath,
cacheFilePath: el.cacheFilePath,
status: el.status,
size: el.fileSize
} as fileListType
});
this.filelist = listfile
return listfile
}
/**
* 返回一个空文件列表数组
*/
clear() : fileListType[] {
this.filelist = [] as fileListType[];
HxFilesSytem!.clear();
return [] as fileListType[]
}
copyFileToPath(item : xFileSListType, path : string) : Promise<xFileSListType | null> {
return Promise.resolve(item);
}
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array/>
</dict>
</plist>
@@ -0,0 +1,3 @@
{
"deploymentTarget": "11"
}
@@ -0,0 +1,197 @@
import { xFileSListType,fileListType } from "../interface"
import { UIDocumentPickerViewController, UIDocumentPickerMode, UIDocumentPickerDelegate, UIViewController } from "UIKit"
import { URL, URLResourceKey, URLResourceValues, Data, FileManager } from "Foundation"
import { UTType } from "UniformTypeIdentifiers"
import Swift from "Swift"
function getUid(rdix = 1, length = 12) : string {
let ix = "";
// #ifdef APP
ix = Math.floor(Math.random() * rdix * Math.floor(Math.random() * Date.now())).toString().substring(0, length as Int);
// #endif
return ix;
}
let filelistAll = [] as fileListType[]
export class xFileSystem {
mimeTypeFilter : string[] = []
filelist = [] as fileListType[]
multiple : boolean = true;
actionSeleCtedFiles : ActionsClass | null = null
maxFileSize = 30 * 1024 * 1024;
constructor(typeFilter : string[] | null, tempmultiple : boolean | null, maxSize : number | null) {
if (typeFilter != null) {
let tms = typeFilter! as string[];
this.mimeTypeFilter = tms;
}
if (tempmultiple != null) {
let tm = tempmultiple as boolean;
this.multiple = tm
}
if (maxSize != null) {
this.maxFileSize = maxSize!
}
}
destory(){
}
openDocument(callfun : (list : any) => void) {
let _this = this;;
DispatchQueue.main.async(execute = () : void => {
_this.actionSeleCtedFiles = new ActionsClass((files : URL[]) => {
let keysToFetch = new Swift.Set<URLResourceKey>();
keysToFetch.insert(URLResourceKey.totalFileAllocatedSizeKey)
// 获得当前用户的缓存目录路径主目录。
let userDir = FileManager.default.urls(for = FileManager.SearchPathDirectory.cachesDirectory, in = FileManager.SearchPathDomainMask.userDomainMask).first!
for (let item in files) {
let ditem = item;
try {
let ds = UTSiOS.try(ditem.resourceValues(forKeys = keysToFetch), "?")
if (ds != null) {
let fSize = ds?.totalFileAllocatedSize ?? 0
let fName = ditem.lastPathComponent;
let fPath = ditem.absoluteString;
let fType = fName.substring((fName.lastIndexOf('.') + 1).toInt())
let realFilePath = ''
let cacheFilePath = fPath
let status = 0
let ifleid = getUid(1, 8);
let tempath = UTSiOS.getDataPath() as string
let destinationFileName = ifleid + "." + fType
let destinationURL = userDir.appendingPathComponent(destinationFileName)
let real = UTSiOS.try(FileManager.default.copyItem(at = ditem, to = destinationURL), "?");
let itemobj = {
name: fName,
type: fType,
id: ifleid,
uri:"",
realFilePath: destinationFileName,
cacheFilePath: destinationFileName,
size: fSize,
status: fSize > _this.maxFileSize ? 4 : 0
} as fileListType;
if(!_this.isInfilelist(itemobj)){
_this.filelist.push(itemobj)
filelistAll.push(itemobj)
}
}
} catch (e) {
//TODO handle the exception
}
}
let list = [] as string[]
for (file in _this.filelist) {
list.push(JSON.stringify(file)!)
}
callfun(JSON.stringify(list))
_this.filelist = [] as fileListType[]
})
try {
if (UTSiOS.available("iOS 14.0, *")) {
let filter : UTType[] = [] as UTType[];
for (let i = 0; i < _this.mimeTypeFilter.length; i++) {
let contentType = new UTType(filenameExtension = _this.mimeTypeFilter[i])
if (contentType != null) {
filter.push(new UTType(filenameExtension = _this.mimeTypeFilter[i])!)
}
}
if(filter.length==0){
filter.push(UTType.item)
}
let documentPickerController = new UIDocumentPickerViewController(forOpeningContentTypes = filter,asCopy = true)
documentPickerController.delegate = _this.actionSeleCtedFiles
documentPickerController.allowsMultipleSelection = _this.multiple // 是否允许选择多个文件
UTSiOS.getCurrentViewController().present(documentPickerController, animated = true, completion = null)
} else {
let documentPickerController = new UIDocumentPickerViewController(documentTypes = ['public.data'], in = UIDocumentPickerMode.import)
documentPickerController.delegate = _this.actionSeleCtedFiles
documentPickerController.allowsMultipleSelection = _this.multiple // 是否允许选择多个文件
UTSiOS.getCurrentViewController().present(documentPickerController, animated = true, completion = null)
}
} catch (e) {
console.error(e)
}
})
}
//读取文件
copyFileToPath(item : xFileSListType, basedir : string) : string {
// let filepath = ''
// for (file in this.filelist) {
// if (file.id == id) {
// filepath = file.cacheFilePath
// break;
// }
// }
return item.cacheFilePath;
}
isInfilelist(file:fileListType):boolean{
return filelistAll.some((el):boolean=>{
return el.name==file.name&&el.type==file.type
})
}
/**
* 返回一个空文件列表数组
*/
clear():fileListType[]{
filelistAll = [] as fileListType[];
this.filelist = [] as fileListType[];
return [] as fileListType[]
}
// ios不作删除自行管理缓存文件。
remove(id : string) : fileListType[] {
let index = -1;
for(let i=0;i<filelistAll.length;i++){
let item = filelistAll[i]
if(item.id==id){
filelistAll.splice(i,1)
break;
}
}
return filelistAll.map((el):fileListType=>{
return {
name:el.name,
type:el.type,
id:el.id,
uri:'',
realFilePath:el.realFilePath,
cacheFilePath:el.cacheFilePath,
size:el.size,
status:el.status
} as fileListType
});
}
}
@MainActor
class ActionsClass implements UIDocumentPickerDelegate {
callSuccessFiles = (files : URL[]) => { }
constructor(call : (files : URL[]) => void) {
super()
this.callSuccessFiles = call;
}
documentPicker(controller : UIDocumentPickerViewController, @argumentLabel("didPickDocumentsAt") urls : URL[]) {
this.callSuccessFiles(urls)
}
documentPickerWasCancelled(controller : UIDocumentPickerViewController) {
console.log("User cancelled the document picker")
}
}
+28
View File
@@ -0,0 +1,28 @@
export type xFileSListType = {
name:string,
type:string,
id:string,
uri:string,
/** web平台文件对象 */
file?:any,
/** 个人用户自定数据 */
request?:any,
/** 安卓机/ios机下面的真实sd路径 */
realFilePath:string,
/** uniapp应用协议文件路径,可用uni.getFileSystemManager进行删除或者查询到 */
cacheFilePath:string,
/** 文件大小 */
size?:number
/**
* 0等待,1失败,2成功,3上传中,4超过大小。
*/
status?:0|1|2|3|4
}
// #ifndef MP
export type fileListType = xFileSListType
// #endif
// #ifdef MP
export type fileListType = any
// #endif
@@ -0,0 +1,123 @@
export { xFileSListType } from "../interface"
export type fileListType = any;
type xFileSNameTYpe = {
name:string,
type:string,
uri:string,
id:string,
file:any,
size:number,
status:number
}
// type callReadBackFun = (byte:ByteArray)=>void
/**
* 由于安卓隐私的问题。实际得到的所有文件 其实 都不是真实地址。因此你想要得到真实的地址,你就得复制到你的应用沙盒中。
*/
export class xFileSystem {
mimeTypeFilter = [] as string[];
filelist = [] as xFileSNameTYpe[]
inputDom:null|HTMLInputElement = null;
multiple:boolean = true;
maxFileSize:number = 30*1024*1024
constructor(typeFilter:string|null,tempmultiple:boolean|null,maxSize:number|null){
if(typeFilter!=null){
let filter : string[] = [] as string[];
let tms = typeFilter! as string[]
for (let i = 0; i < tms.length; i++) {
filter.push("."+tms[i])
}
if(filter.length==0){
filter.push("*/*")
}
this.mimeTypeFilter = filter
}
if(tempmultiple!=null){
this.multiple = tempmultiple!
}
if(maxSize!=null){
this.maxFileSize = maxSize!
}
}
isInfilelist(file:xFileSNameTYpe):boolean{
return this.filelist.some((el):boolean=>{
return el.name==file.name&&el.type==file.type
})
}
destory(){
}
openDocument(call:(list:xFileSListType[])=>void){
let _this = this;
let filetype = this.mimeTypeFilter[0].split('/')[0]
if(filetype=='*'){
filetype = 'all'
}
wx.chooseMessageFile({
count:100,
type:filetype,
success(result) {
let filelist = result.tempFiles
for(let i=0;i<filelist.length;i++){
let item = filelist[i];
let fileitem = {
name:item.name,
type:item.type.substring(item.type.lastIndexOf("/")+1),
uri:"",
id:Math.floor(Math.random() * 1 * Math.floor(Math.random() * Date.now())).toString().substring(0, 10),
file:item,
size:item.size,
status:item.size>_this.maxFileSize?4:0,
} as xFileSNameTYpe
if(!_this.isInfilelist(fileitem)){
_this.filelist.push(fileitem)
}
}
call(_this.filelist.map((el)=>{
return {
name:el.name,
type:el.type,
id:el.id,
request:null,
realFilePath:el.file.path,
cacheFilePath:el.file.path,
size:el.size,
status:el.status
}
}))
},
fail(er) {
console.error(er)
}
})
}
/**
* 删除一个文件条目
* @param {string} id - 文件id
* @returns {fileListType[]} 最新的文件列表
*/
remove(id:string):xFileSListType[]{
return this.filelist.filter(el=>el.id!=id)
}
/**
* 返回一个空文件列表数组
*/
clear():xFileSListType[]{
this.filelist = [] as xFileSNameTYpe[];
return [] as xFileSListType[]
}
copyFileToPath(item:xFileSListType,path:string):Promise<xFileSListType|null>{
return Promise.resolve(item)
}
}
+130
View File
@@ -0,0 +1,130 @@
export { xFileSListType } from "../interface"
export type fileListType = xFileSListType;
type xFileSNameTYpe = {
name:string,
type:string,
uri:string,
id:string,
file:any,
size:number,
status:number
}
// type callReadBackFun = (byte:ByteArray)=>void
/**
* 由于安卓隐私的问题。实际得到的所有文件 其实 都不是真实地址。因此你想要得到真实的地址,你就得复制到你的应用沙盒中。
*/
export class xFileSystem {
mimeTypeFilter = [] as string[];
filelist = [] as xFileSNameTYpe[]
inputDom:null|HTMLInputElement = null;
multiple:boolean = true;
maxFileSize:number = 30*1024*1024
constructor(typeFilter:string|null,tempmultiple:boolean|null,maxSize:number|null){
if(typeFilter!=null){
let filter : string[] = [] as string[];
let tms = typeFilter! as string[]
for (let i = 0; i < tms.length; i++) {
filter.push("."+tms[i])
}
if(filter.length==0){
filter.push("*/*")
}
this.mimeTypeFilter = filter
}
if(tempmultiple!=null){
this.multiple = tempmultiple!
}
if(maxSize!=null){
this.maxFileSize = maxSize!
}
}
isInfilelist(file:xFileSNameTYpe):boolean{
return this.filelist.some((el):boolean=>{
return el.name==file.name&&el.type==file.type
})
}
destory(){
}
openDocument(call:(list:fileListType[])=>void){
if(this.inputDom!=null){
this.inputDom!.remove()
}
let _this = this;
let inputdom = document.createElement("input")
inputdom.type = 'file';
inputdom.accept = this.mimeTypeFilter.join(", ");
inputdom.style.setProperty('position','absolute')
inputdom.style.setProperty('left','-200%')
inputdom.style.setProperty('top','-200%')
inputdom.style.setProperty('opacity','0')
if(this.multiple){
inputdom.multiple = "multiple"
}
inputdom.onchange = function(evt){
let filelist = inputdom.files||[] as File[];
for(let i=0;i<filelist.length;i++){
let item = filelist[i];
let fileitem = {
name:item.name,
type:item.type.substring(item.type.lastIndexOf("/")+1),
uri:"",
id:Math.floor(Math.random() * 1 * Math.floor(Math.random() * Date.now())).toString().substring(0, 10),
file:item
size:item.size,
status:item.size>_this.maxFileSize?4:0
} as xFileSNameTYpe
if(!_this.isInfilelist(fileitem)){
_this.filelist.push(fileitem)
}
}
call(_this.filelist.map((el)=>{
return {
name:el.name,
type:el.type,
file:el.file,
id:el.id,
request:null,
realFilePath:"",
cacheFilePath:"",
size:el.size,
status:el.size>_this.maxFileSize?4:0
}
}))
}
inputdom.click()
}
/**
* 删除一个文件条目
* @param {string} id - 文件id
* @returns {fileListType[]} 最新的文件列表
*/
remove(id:string):fileListType[]{
return [] as fileListType[]
}
/**
* 返回一个空文件列表数组
*/
clear():fileListType[]{
this.filelist = [] as xFileSNameTYpe[];
return [] as fileListType[]
}
copyFileToPath(item:fileListType,path:string):Promise<fileListType|null>{
return Promise.resolve(item)
}
}