✨家人们记得点个账号关注,会持续发布大前端领域技术文章💕 🍃
一、文件上传:从基础到企业级方案
文件上传是鸿蒙应用开发的核心场景(如头像上传、附件提交、图片分享),鸿蒙基于沙箱安全机制,要求上传文件必须先存入应用沙箱目录(cacheDir),再通过系统 API 提交。以下是图片上传、拍照上传、任意文件上传的完整实现,附企业级封装工具类。
1.1 图片上传(媒体库选择)
思路
1、使用Picker选择媒体库的图片与视频 (返回一个临时的图片地址 file:// 咱们可以直接预览 也可以继续向后走 拿到服务器地址再预览)
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
...
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then().catch()
2、把图片拷贝到应用的缓存目录 (不支持直接通过相册本地路径请求接口,仅支持通过缓存目录上传 context.cacheDir)
- 2.1 通过 openSync 读取 媒体库文件
- 2.2 通过 fileIo.copyFileSync(file.fd媒体文件, copyFilePath沙箱文件)
细节:copyFilePath沙箱中文件生成规则必须唯一
fileType
fileName 时间戳 + 随机数1000~9999 + . 后缀 (别忘了含后缀)
copyFilePath
3、上传文件 request.uploadFile到服务器
request.uploadFile(上下文信息, 匹配对象{
请求方式method: 'POST'
请求地址url: '',
请求头
header: {},
请求参数
files: [
{ name: '参数名', uri: "internal://cache/沙箱目录下的文件名含后缀", type:'',filename:'文件名含后缀' }
]
})
.then(uploadTask => {
// uploadTask.on(类型, 回调函数)
// progress 订阅上传任务进度事件,使用callback异步回调。
// headerReceive 服务器返回的
// complete 订阅上传任务完成
// fail 订阅上传任务失败
})
- 使用Picker选择媒体库的图片与视频
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
@State localFilePath:string = ''
build() {
Column() {
Button('拉起媒体库·选择照片').onClick(() => {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
let uris: Array<string> = [];
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
uris = photoSelectResult.photoUris; // 数组 里面是一个个file 也就是本地媒体库图片地址 咱们可以直接预览
this.localFilePath = photoSelectResult.photoUris[0]
console.info('photoViewPicker.select to file succeed and uris are:' + uris); // file://media/Photo/947/IMG_1742896185_814/IMG_814.jpg
}).catch((err: BusinessError) => {
console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
})
Image(this.localFilePath).width('100%')
}
}
}
- 把图片拷贝到应用的沙箱目录
当前上传应用文件功能,不支持直接上传本地相册的文件,仅支持上传应用缓存文件路径(cacheDir)下的文件。
原因:考虑到安全,程序不能直接访问相册所有图片 仅仅支持用户选择后拷贝到自己的沙箱目录下 隔离性
// 把相册路径拷贝到缓存路径下(沙箱路径下)
const fileType = 'jpg'
const fileName = Date.now() + '_' + (Math.floor(Math.random() * (99999 - 11111)) + 11111) + '.' + fileType
const copyFilePath = getContext(this).cacheDir + '/' + fileName
const file = fileIo.openSync(this.uris[0], fileIo.OpenMode.READ_ONLY) // file://media/Photo/2/IMG_1735796348_001/00.jpg
fileIo.copyFileSync(file.fd, copyFilePath)
console.info('cacheDir: ' + copyFilePath);
- 上传图片到服务器
准备好参数调用request.uploadFile()获得上传对象 uploader
给uploader对象注册progress事件,监听上传进度 requestRes.on("progress", (uploadedSize: number, totalSize: number)=>{})
request.uploadFile(上下文信息, 匹配对象{
请求方式method: 'POST'
请求地址url: '',
请求头
header: {},
请求参数
files: [
{ name: '参数名', uri: "internal://cache/沙箱目录下的文件名含后缀", type:'',filename:'文件名含后缀' }
]
})
.then(uploadTask => {
// uploadTask.on(类型, 回调函数)
// progress 订阅上传任务进度事件,使用callback异步回调。
// headerReceive 服务器返回的
// complete 订阅上传任务完成
// fail 订阅上传任务失败
})
- 示例代码
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { fileIo } from '@kit.CoreFileKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
export interface UploadFileType {
database: string;
url: string;
preview: string;
}
export interface PostUploadFileResType {
state: number;
msg: string;
data: UploadFileType;
}
@Entry
@Component
struct Index {
@State localFilePath:string = ''
build() {
Column() {
Button('选择的照片拷贝到沙箱').onClick(() => {
// 一、拉起媒体库选择照片
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
let uris: Array<string> = [];
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
uris = photoSelectResult.photoUris; // 数组 里面是一个个file 也就是本地媒体库图片地址 咱们可以直接预览
this.localFilePath = photoSelectResult.photoUris[0]
console.info('photoViewPicker.select to file succeed and uris are:' + uris); // file://media/Photo/947/IMG_1742896185_814/IMG_814.jpg
// 二、拷贝到沙箱目录
const fileType = 'jpg'
const fileName = Date.now() + '_' + Math.floor( Math.random() * (9999-1000+1)+1000 ) + '.' + fileType
const copyFilePath = getContext(this).cacheDir + '/' + fileName
// - 2.1 通过 openSync 读取 媒体库文件
const file = fileIo.openSync(photoSelectResult.photoUris[0], fileIo.OpenMode.READ_ONLY)
// - 2.2 通过 fileIo.copyFileSync(file.fd, copyFilePath)
// fileIo.copyFileSync(原文件唯一标识, 沙箱目录及文件名)
fileIo.copyFileSync(file.fd, copyFilePath)
// 细节:copyFilePath沙箱中文件生成规则必须唯一
// fileType
// fileName 时间戳 + 随机数1000~9999 + . 后缀
// copyFilePath
console.log('最终沙箱目录:', copyFilePath)
// 三、上传图片到服务器
request.uploadFile(getContext(this), {
method: 'POST',
url: 'http://123.56.141.187:8001/upload/create',
header: {},
files: [
{name: 'file', uri:'internal://cache/'+fileName, type: fileType,filename:fileName }
],
data: []
})
.then((uploadTask) => {
uploadTask.on('progress', (uploadedSize: number, totalSize: number) => {
console.info("upload totalSize:" + totalSize + " uploadedSize:" + uploadedSize);
})
uploadTask.on('headerReceive', (headers:ESObject) => {
const serverData: PostUploadFileResType = JSON.parse(headers?.body)
console.log('服务器数据:', serverData.state)
console.log('服务器数据:', serverData.msg)
console.log('服务器数据:', serverData.data.database) // 图片名
console.log('服务器数据:', serverData.data.preview) // 完整网址+图片
})
uploadTask.on('complete', () => {
console.log('上传完成')
})
uploadTask.on('fail', () => {
console.log('上传失败')
})
})
// 三、上传图片到服务器 end
}).catch((err: BusinessError) => {
console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
})
Image(this.localFilePath).width('100%')
}
}
}
1.2 拍照上传
API参考:developer.huawei.com/consumer/cn…
import { cameraPicker as picker } from '@kit.CameraKit';
import { camera } from '@kit.CameraKit';
import { common } from '@kit.AbilityKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
let mContext = getContext(this) as common.Context;
export interface UploadFileType {
database: string;
url: string;
preview: string;
}
export interface PostUploadFileResType {
state: number;
msg: string;
data: UploadFileType | null;
}
@Entry
@Component
struct Index {
build() {
Button('拍照').onClick(async () => {
try {
const pickerProfile: picker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
const pickerResult: picker.PickerResult = await picker.pick(mContext, [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO], pickerProfile);
console.log("the pick pickerResult is:" + JSON.stringify(pickerResult)); // 返回一个对象, 里面的【resultUri】本地的媒体地址 file:...
console.log("the pick pickerResult is:" + pickerResult.resultUri); // file://media/Photo/2418/IMG_1748244688_2173/IMG_20250526_152948.jpg
// 拷贝到沙箱
// const fileType = 'jpg'
const fileType = pickerResult.resultUri.split('.').pop() // 'file://medxxx250526_48.jpg'.split('.').pop()
const fileName = Date.now() + '_' + Math.floor( Math.random() * (9999-1000+1)+1000 ) + '.' + fileType
const copyFilePath = getContext(this).cacheDir + '/' + fileName
// - 2.1 通过 openSync 读取 媒体库文件
const file = fileIo.openSync(pickerResult.resultUri, fileIo.OpenMode.READ_ONLY)
// - 2.2 通过 fileIo.copyFileSync(file.fd, copyFilePath)
// fileIo.copyFileSync(原文件唯一标识, 沙箱目录及文件名)
fileIo.copyFileSync(file.fd, copyFilePath)
// 细节:copyFilePath沙箱中文件生成规则必须唯一
// fileType
// fileName 时间戳 + 随机数1000~9999 + . 后缀
// copyFilePath
console.log('最终沙箱目录:', copyFilePath)
// 请求接口(但是接口有问题 超过2m不行 但是手机一拍就是2M)
request.uploadFile(getContext(this), {
method: 'POST',
url: 'http://123.56.141.187:8001/upload/create',
header: {},
files: [
{name: 'file', uri:'internal://cache/'+fileName, type: fileType,filename:fileName }
],
data: []
})
.then((uploadTask) => {
uploadTask.on('progress', (uploadedSize: number, totalSize: number) => {
console.info("upload totalSize:" + totalSize + " uploadedSize:" + uploadedSize);
})
uploadTask.on('headerReceive', (headers:ESObject) => {
const serverData: PostUploadFileResType = JSON.parse(headers?.body)
console.log('服务器数据:', serverData.state)
console.log('服务器数据:', serverData.msg)
console.log('服务器数据:', serverData.data?.database) // 图片名
console.log('服务器数据:', serverData.data?.preview) // 完整网址+图片
})
uploadTask.on('complete', () => {
console.log('上传完成')
})
uploadTask.on('fail', () => {
console.log('上传失败')
})
})
// 三、上传图片到服务器 end
} catch (error) {
let err = error as BusinessError;
console.error(`the pick call failed. error code: ${err.code}`);
}
})
}
}
1.3 其他文件 - 选择用户文件
tmp00002.zhaodashen.cn/hello.txt
tmp00002.zhaodashen.cn/hello2.docx
tmp00002.zhaodashen.cn/hello3.pdf
tmp00002.zhaodashen.cn/hello4.zip
developer.huawei.com/consumer/cn…
text/zip/pdf/docx
import { fileIo, picker } from '@kit.CoreFileKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { common } from '@kit.AbilityKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
export interface UploadFileType {
database: string;
url: string;
preview: string;
}
export interface PostUploadFileResType {
state: number;
msg: string;
data: UploadFileType;
}
@Entry
@Component
struct Index {
build() {
Button('上传附件').onClick((event: ClickEvent) => {
const documentSelectOptions = new picker.DocumentSelectOptions();
// 选择文档的最大数目(可选)。
documentSelectOptions.maxSelectNumber = 5;
// 指定选择的文件或者目录路径(可选)。
// documentSelectOptions.defaultFilePathUri = "file://docs/storage/Users/currentUser/test";
// 选择文件的后缀类型['后缀类型描述|后缀类型'](可选,不传该参数,默认不过滤,即显示所有文件),若选择项存在多个后缀名,则每一个后缀名之间用英文逗号进行分隔(可选),后缀类型名不能超过100。此外2in1设备支持通过通配符方式['所有文件(*.*)|.*'],表示为显示所有文件,手机暂不支持该配置。
documentSelectOptions.fileSuffixFilters = ['图片(.png, .jpg)|.png,.jpg', '文档|.txt', '视频|.mp4', '.pdf'];
//选择是否对指定文件或目录授权,true为授权,当为true时,defaultFilePathUri为必选参数,拉起文管授权界面;false为非授权(默认为false),拉起常规文管界面(可选),仅支持2in1设备。
documentSelectOptions.authMode = false;
// 创建文件选择器实例
const documentViewPicker = new picker.DocumentViewPicker(getContext(this));
documentViewPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
//文件选择成功后,返回被选中文档的uri结果集。
console.info('documentViewPicker.select to file succeed and uris are:' + documentSelectResult);
// 二、拷贝到沙箱目录
// 拷贝到沙箱
// const fileType = 'jpg'
const fileType = documentSelectResult[0].split('.').pop() // 'file://medxxx250526_48.jpg'.split('.').pop()
const fileName = Date.now() + '_' + Math.floor( Math.random() * (9999-1000+1)+1000 ) + '.' + fileType
const copyFilePath = getContext(this).cacheDir + '/' + fileName
// - 2.1 通过 openSync 读取 媒体库文件
const file = fileIo.openSync(documentSelectResult[0], fileIo.OpenMode.READ_ONLY)
// - 2.2 通过 fileIo.copyFileSync(file.fd, copyFilePath)
// fileIo.copyFileSync(原文件唯一标识, 沙箱目录及文件名)
fileIo.copyFileSync(file.fd, copyFilePath)
// 细节:copyFilePath沙箱中文件生成规则必须唯一
// fileType
// fileName 时间戳 + 随机数1000~9999 + . 后缀
// copyFilePath
console.log('最终沙箱目录:', copyFilePath)
// 请求接口(但是接口有问题 超过2m不行 但是手机一拍就是2M)
request.uploadFile(getContext(this), {
method: 'POST',
url: 'http://123.56.141.187:8001/upload/create',
header: {},
files: [
{name: 'file', uri:'internal://cache/'+fileName, type: fileType,filename:fileName }
],
data: []
})
.then((uploadTask) => {
uploadTask.on('progress', (uploadedSize: number, totalSize: number) => {
console.info("upload totalSize:" + totalSize + " uploadedSize:" + uploadedSize);
})
uploadTask.on('headerReceive', (headers:ESObject) => {
const serverData: PostUploadFileResType = JSON.parse(headers?.body)
console.log('服务器数据:', serverData.state)
console.log('服务器数据:', serverData.msg)
console.log('服务器数据:', serverData.data?.database) // 图片名
console.log('服务器数据:', serverData.data?.preview) // 完整网址+图片
})
uploadTask.on('complete', () => {
console.log('上传完成')
})
uploadTask.on('fail', () => {
console.log('上传失败')
})
})
// 三、上传图片到服务器 end
}).catch((err: BusinessError) => {
console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
})
}
}
1.4 切片上传/断点续传
后续讲解,也可以私聊付费解答
1.5 对象存储 实战
- 1. 需求
编辑
cloud.tencent.com/document/pr…
- 2. 由来
明确1:公司开发项目必须有上传图片,这些图片最初就和接口也就是java代码放在一起
明确2:一台服务器每秒读写有上限 =》 就好比厕所坑位 同时蹲坑有上限
明确3:考虑到性能问题 图片、样式、接口代码也就是java代码、包括数据库 得放到不同服务器
方案1;每个公司自己搞一个服务器
方案2:用服务器供应商的 (对象存储产品-专门放图片视频等等、云数据库)
回答:选择方案2,还有额外好处例如CDN、例如图片裁剪水印
CDN
编辑
大致使用流程
1-下模块
2-导入模块
3-修改配置信息
- 3. 腾讯云
步骤1:注册账号 cloud.tencent.com/document/pr…
步骤2:个人中心、访问管理、用户列表、新建用户 -》 点击进去 操作 API密钥 (新增就可以看到secretID/secretKey)
步骤3:左上角搜索产品对象存储、创建存储桶列表 console.cloud.tencent.com/cos/bucket
步骤4:根据步骤1、2、3操作 cloud.tencent.com/document/pr…
import { CosError, QCloudCredential } from '@tencentcloud/cos';
import { CosXmlBaseService, CosXmlServiceConfig } from '@tencentcloud/cos';
import { PutObjectRequest, UploadTask } from '@tencentcloud/cos';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { CosXmlUploadTaskResult } from '@tencentcloud/cos/src/main/ets/transfer/UploadTask';
import log from '@open/log';
// 获取临时密钥(业务层控制获取的方式)
let credential : QCloudCredential= new QCloudCredential();
credential.secretID = "AKIDd8UebLvISSJ883qiQxJix0xjKl04zkJg";
credential.secretKey = "HPL6iBVdZIqEfPxLWdp5EGD2lLlHnBNS";
// credential.token = "token";
// startDate和expirationDate均为Date类型,此处通过毫秒时间戳构造Date
// credential.startDate = new Date();
// credential.expirationDate = new Date();
@Entry
@Component
struct Test {
@State img:string | undefined = ''
build() {
Column() {
Image(this.img).width(300)
Button('文档上传').onClick(async () => {
log.init({close:false,tag:"✨"})
let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
PhotoSelectOptions.maxSelectNumber = 1;
let photoPicker = new photoAccessHelper.PhotoViewPicker();
let photoSelectResult: photoAccessHelper.PhotoSelectResult = await photoPicker.select(PhotoSelectOptions);
const localPath:string = photoSelectResult.photoUris[0];
log.info('00000')
log.info(localPath)
// 服务配置
let cosXmlServiceConfig = new CosXmlServiceConfig("ap-beijing"); // ✅
// 初始化默认的Service
CosXmlBaseService.initDefaultService(
// this.context.getApplicationContext(),
getContext(this),
cosXmlServiceConfig,
async () => credential
)
// 任何CosXmlRequest都支持这种方式,例如上传PutObjectRequest、下载GetObjectRequest、删除DeleteObjectRequest等
// 以下用上传进行示例
let putRequest = new PutObjectRequest("slj-1257200217", "temp1111111.jpg", localPath); // ✅
// credential为第一步“初始化密钥”中获取到的单次临时密钥
putRequest.credential = credential;
let task: UploadTask = CosXmlBaseService.default().upload(putRequest);
task.onResult = {
onSuccess: (request, result: CosXmlUploadTaskResult) => {
// const match = result.accessUrl!.match(/user/.*/)
// todo 上传成功后的逻辑
// this.upload({avatar:result.picUploadResult?.originalInfo?.key as string})
// this.upload({ avatar: match![0] })
log.info(1111)
log.info(result.accessUrl)
this.img = result.accessUrl
},
onFail: (request, error: CosError) => {
// todo 上传失败后的逻辑
log.info(2222)
log.info(error)
}
}
task.start();
// ===================
})
}
}
}
- 4. 阿里云
- 5. 七牛云
developer.qiniu.com/kodo/12669/…
1.6 周边语法 统计文件大小
比特(bit)
字节(Bytes)
千字节(KB)
兆字节(MB)
千兆字节(GB)
太字节(TB)
let stat = fileIo.statSync(copyFilePath);
console.info("get file info succeed, the size of file is " + JSON.stringify(stat));
console.info("get file info succeed, the size of file is " + stat.size/1024/1024);
二、下载模块、Zip模块
2.1 基础使用
tmp00002.zhaodashen.cn/hello.txt
tmp00002.zhaodashen.cn/hello2.docx
tmp00002.zhaodashen.cn/hello3.pdf
tmp00002.zhaodashen.cn/hello4.zip
developer.huawei.com/consumer/cn…
developer.huawei.com/consumer/cn…
import { BusinessError, request } from '@kit.BasicServicesKit'
import { fileIo as fs } from '@kit.CoreFileKit'
import { zlib } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
private filePath:string = getContext().filesDir + '/'
@State currentSize:number = 0
@State totalSize:number = 0
unlinkFile(fileName:string) {
if ( fs.accessSync(this.filePath+fileName) ) {
fs.unlinkSync(this.filePath+fileName)
}
}
downloadFile(url:string) {
const fileName:string = url.split('/').pop() as string
// fs模块检查文件是否存在 存在就删除
this.unlinkFile(fileName)
// 下载
request.downloadFile(getContext(), {
url,
filePath: this.filePath + fileName // 字符串变数组 [...,'node-v22.16.0.pkg'] 通过pop弹出最后数据 pop是一个函数 返回弹出的数据
}).then(uploadTask => {
uploadTask.on("progress", (receivedSize: number, totalSize: number) => {
// receivedSize 当前下载字节
// totalSize 总字节
console.log('数据:', receivedSize, totalSize)
this.currentSize = receivedSize
this.totalSize = totalSize
})
uploadTask.on("complete", () => {
console.log('下载完成')
})
})
}
build() {
Column() {
Button('下载文件txt').onClick(() => {
this.downloadFile('http://tmp00002.zhaodashen.cn/hello.txt')
})
Progress({
value: this.currentSize,
total: this.totalSize,
style:ProgressStyle.Ring
})
Button('下载文件node安装包').onClick(() => {
this.downloadFile('https://npmmirror.com/mirrors/node/v22.16.0/node-v22.16.0.pkg')
})
Button('下载文件zip').onClick(() => {
this.downloadFile('http://tmp00002.zhaodashen.cn/hello4.zip')
})
Button('zip解压').onClick(() => {
let inFile = this.filePath + 'hello4.zip';
let outFileDir = getContext().cacheDir
let options: zlib.Options = {
level: zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION
};
try {
zlib.decompressFile(inFile, outFileDir, options, (errData: BusinessError) => {
if (errData !== null) {
console.error(`errData is errCode:${errData.code} message:${errData.message}`);
}
})
} catch (errData) {
let code = (errData as BusinessError).code;
let message = (errData as BusinessError).message;
console.error(`errData is errCode:${code} message:${message}`);
}
// .....
})
}
}
}
2.2 封装使用
封装
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
import { fileIo, picker } from '@kit.CoreFileKit';
import { cameraPicker } from '@kit.CameraKit';
import { camera } from '@kit.CameraKit';
export interface UploadFileType {
database: string;
url: string;
preview: string;
}
export interface PostUploadFileResType {
state: number;
msg: string;
data: UploadFileType;
}
class FileUploadDownloadUtil {
// 1 拿到媒体资源本地路径
chooseImage():Promise<string[]> {
return new Promise((resolve, reject) => {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
let uris: Array<string> = [];
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then((photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
uris = photoSelectResult.photoUris; // 数组 里面是一个个file 也就是本地媒体库图片地址 咱们可以直接预览
// this.localFilePath = photoSelectResult.photoUris[0]
resolve(uris)
console.info('photoViewPicker.select to file succeed and uris are:' + uris); // file://media/Photo/947/IMG_1742896185_814/IMG_814.jpg
}).catch((err: BusinessError) => {
reject([])
console.error(`Invoke photoViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
})
}
chooseFile():Promise<string[]> {
return new Promise((resolve, reject) => {
const documentSelectOptions = new picker.DocumentSelectOptions();
// 选择文档的最大数目(可选)。
documentSelectOptions.maxSelectNumber = 5;
// 指定选择的文件或者目录路径(可选)。
// documentSelectOptions.defaultFilePathUri = "file://docs/storage/Users/currentUser/test";
// 选择文件的后缀类型['后缀类型描述|后缀类型'](可选,不传该参数,默认不过滤,即显示所有文件),若选择项存在多个后缀名,则每一个后缀名之间用英文逗号进行分隔(可选),后缀类型名不能超过100。此外2in1设备支持通过通配符方式['所有文件(*.*)|.*'],表示为显示所有文件,手机暂不支持该配置。
documentSelectOptions.fileSuffixFilters = ['图片(.png, .jpg)|.png,.jpg', '文档|.txt', '视频|.mp4', '.pdf'];
//选择是否对指定文件或目录授权,true为授权,当为true时,defaultFilePathUri为必选参数,拉起文管授权界面;false为非授权(默认为false),拉起常规文管界面(可选),仅支持2in1设备。
documentSelectOptions.authMode = false;
// 创建文件选择器实例
const documentViewPicker = new picker.DocumentViewPicker(getContext(this));
documentViewPicker.select(documentSelectOptions).then((documentSelectResult: Array<string>) => {
//文件选择成功后,返回被选中文档的uri结果集。
console.info('documentViewPicker.select to file succeed and uris are:' + documentSelectResult);
resolve(documentSelectResult)
}).catch((err: BusinessError) => {
reject([])
console.error(`Invoke documentViewPicker.select failed, code is ${err.code}, message is ${err.message}`);
})
})
}
chooseCamera():Promise<string> {
return new Promise(async (resolve, reject) => {
try {
const pickerProfile: cameraPicker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
const pickerResult: cameraPicker.PickerResult = await cameraPicker.pick(getContext(), [cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.VIDEO], pickerProfile);
console.log("the pick pickerResult is:" + JSON.stringify(pickerResult)); // 返回一个对象, 里面的【resultUri】本地的媒体地址 file:...
console.log("the pick pickerResult is:" + pickerResult.resultUri); // file://media/Photo/2418/IMG_1748244688_2173/IMG_20250526_152948.jpg
resolve(pickerResult.resultUri)
} catch (error) {
reject('')
let err = error as BusinessError;
console.error(`the pick call failed. error code: ${err.code}`);
}
})
}
// 2 拷贝到沙箱
fileMoveSx(filePath:string):[string, string] {
const fileType = filePath.split('.').pop() as string
const fileName = Date.now() + '_' + Math.floor( Math.random() * (9999-1000+1)+1000 ) + '.' + fileType
const copyFilePath = getContext(this).cacheDir + '/' + fileName
// - 2.1 通过 openSync 读取 媒体库文件
const file = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY)
// - 2.2 通过 fileIo.copyFileSync(file.fd, copyFilePath)
// fileIo.copyFileSync(原文件唯一标识, 沙箱目录及文件名)
fileIo.copyFileSync(file.fd, copyFilePath)
// 细节:copyFilePath沙箱中文件生成规则必须唯一
// fileType
// fileName 时间戳 + 随机数1000~9999 + . 后缀
// copyFilePath
console.log('最终沙箱目录:', copyFilePath)
// return ['文件类型', '文件名称']
return [fileType, fileName]
}
// 3 上传文件
upload(filePath:string):Promise<string>{ // 空字符串代表上传失败
// # 3.1 就是1 媒体本地路径
const temp = this.fileMoveSx(filePath)
const fileType = temp[0]
const fileName = temp[1]
// # 3.2 接着请求接口
return new Promise((resolve, reject) => {
// 三、上传图片到服务器
request.uploadFile(getContext(this), {
method: 'POST',
url: 'http://123.56.141.187:8001/upload/create',
header: {},
files: [
{name: 'file', uri:'internal://cache/'+fileName, type: fileType,filename:fileName }
],
data: []
})
.then((uploadTask) => {
uploadTask.on('progress', (uploadedSize: number, totalSize: number) => {
console.info("upload totalSize:" + totalSize + " uploadedSize:" + uploadedSize);
})
uploadTask.on('headerReceive', (headers:ESObject) => {
const serverData: PostUploadFileResType = JSON.parse(headers?.body)
// console.log('服务器数据:', serverData.state)
// console.log('服务器数据:', serverData.msg)
// console.log('服务器数据:', serverData.data?.database) // 图片名
// console.log('服务器数据:', serverData.data?.preview) // 完整网址+图片
resolve(serverData.data?.database)
})
uploadTask.on('complete', () => {
console.log('上传完成')
})
uploadTask.on('fail', () => {
reject('')
console.log('上传失败')
})
})
// 三、上传图片到服务器 end
})
}
// 3 下载文件
download(url:string, filePath:string):Promise<boolean>{ // 沙箱地址
if ( fileIo.accessSync(filePath) ) {
fileIo.unlinkSync(filePath)
}
return new Promise((resolve, reject) => {
// 下载
request.downloadFile(getContext(), {
url,
filePath
}).then(uploadTask => {
uploadTask.on("complete", () => {
console.log('下载完成')
resolve(true)
})
uploadTask.on("fail", () => {
console.log('下载失败')
reject(false)
})
})
})
}
}
export const fileUploadDownloadUtil = new FileUploadDownloadUtil()
使用
import { fileUploadDownloadUtil } from '../utils/FileUploadDownloadUtil'
@Entry
@Component
struct Index {
build() {
Button('上传图片').onClick(async () => {
const filePath:string[] = await fileUploadDownloadUtil.chooseImage()
if (filePath.length) {
const serverData = await fileUploadDownloadUtil.upload(filePath[0])
console.log('- 最终结果:' + serverData)
console.log('- 最终结果:' + serverData)
console.log('- 最终结果:' + serverData)
console.log('- 最终结果:' + serverData)
console.log('- 最终结果:' + serverData)
}
})
}
}
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
✨家人们点个juejin账号关注,会持续发布大前端领域技术文章💕 🍃
^_^ 点关注、不迷路、主播带你学技术 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤