【HarmonyOS NEXT】深入解析HarmonyOS NEXT中的媒体处理功能
在HarmonyOS NEXT中,媒体处理功能是应用开发的核心部分,包括照片上传、拍照上传、文件下载和文件预览。本文将详细介绍这些功能的实现方法和代码细节,帮助你更好地理解和应用HarmonyOS NEXT的API。
目录
HarmonyOS NEXT中的媒体处理功能
1. 照片上传与拍照上传
在HarmonyOS NEXT中,照片上传和拍照上传功能通常涉及权限申请、媒体选择或拍摄以及文件上传三个步骤。
1.1 权限申请
在进行任何媒体操作之前,应用需要获取用户的权限。以下是如何检查和申请媒体读写权限的代码:
import { Permissions } from '@ohos.abilityAccessCtrl';
import { applyPermission } from './permissions';
async function checkAndRequestPermissions() {
const permissions = [Permissions.READ_MEDIA, Permissions.WRITE_MEDIA];
const status = await applyPermission(context, permissions);
if (status) {
console.log('All permissions are granted.');
} else {
console.error('Some permissions are not granted.');
}
}
在这段代码中,我们使用了applyPermission
函数来申请媒体相关的权限。具体逻辑如下:
- 定义权限列表:需要请求读写媒体文件的权限,定义一个包含
Permissions.READ_MEDIA
和Permissions.WRITE_MEDIA
的数组。 - 请求权限:调用
applyPermission
函数请求权限,并等待用户响应。 - 检查权限状态:通过返回值
status
检查用户是否授予了所有请求的权限。如果全部授予,则输出成功信息;否则,输出错误信息。
1.2 从相册选择照片或拍照
使用photoAccessHelper
模块,我们可以方便地从相册选择照片或使用相机拍照。以下是如何实现这一功能的代码:
import { photoAccessHelper } from '@kit.MediaLibraryKit';
async function selectPhotoOrTakePhoto() {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
photoSelectOptions.maxSelectNumber = 1;
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
try {
const photoSelectResult = await photoViewPicker.select(photoSelectOptions);
if (photoSelectResult.photoUris.length > 0) {
const photoUri = photoSelectResult.photoUris[0];
// 处理选中的照片
handleSelectedPhoto(photoUri);
}
} catch (error) {
console.error('Failed to select photo or take photo.', error);
}
}
function handleSelectedPhoto(photoUri) {
// 将照片复制到应用的沙箱目录
copyFileToCache(photoUri, context).then((filePath) => {
// 上传照片
uploadPhoto(filePath);
});
}
具体逻辑如下:
- 设置照片选择选项:
- 创建
PhotoSelectOptions
对象,设置MIMEType
为图像类型,确保用户只能选择照片。 - 设置
maxSelectNumber
为1,表示用户只能选择一张照片。
- 创建
- 创建照片选择器:使用
photoViewPicker
对象来选择照片或拍照。 - 执行选择操作:
- 使用
photoViewPicker.select
方法选择照片,并等待用户响应。 - 如果用户选中了照片,获取选中的照片URI。
- 使用
- 处理选中的照片:
- 调用
handleSelectedPhoto
函数,将选中的照片复制到应用的沙箱目录。 - 如果复制成功,调用
uploadPhoto
函数上传照片。
- 调用
1.3 照片上传
上传照片的实现涉及到构建表单数据并发送HTTP请求。以下是如何实现照片上传的代码:
import { uploadImg } from '../api/User';
function uploadPhoto(filePath) {
const formData = new FormData();
formData.append('file', filePath);
const config = {
headers: {
'Content-Type': 'multipart/form-data',
},
context: context,
};
uploadImg(formData, config).then((response) => {
if (response.data && response.data.url) {
console.log('Photo uploaded successfully.', response.data.url);
}
}).catch((error) => {
console.error('Failed to upload photo.', error);
});
}
具体逻辑如下:
- 构建表单数据:
- 创建
FormData
对象。 - 使用
formData.append
方法将文件路径添加到表单数据中,键名为file
。
- 创建
- 配置请求:
- 创建
config
对象,设置请求头Content-Type
为multipart/form-data
,并传入当前上下文context
。
- 创建
- 发送HTTP请求:
- 调用
uploadImg
函数发送HTTP请求上传照片。 - 如果上传成功,检查
response.data
中的url
属性,输出上传成功的URL。 - 如果上传失败,输出错误信息。
- 调用
ps:uploadImg:
这里的uploadImg 方法本质上是一个封装好的axios请求,最主要的内容是对headers、formData 设置;axios的封装就不在此赘述了。
2. 文件下载
文件下载功能可以通过request
模块实现。以下是如何创建下载任务并监听进度和完成事件的代码:
import { request } from '@kit.BasicServicesKit';
function downloadFile(url, filePath) {
const downloadConfig = {
url: url,
filePath: filePath,
enableMetered: true,
};
request.downloadFile(downloadConfig).then((downloadTask) => {
downloadTask.on('progress', (receivedSize, totalSize) => {
console.info(`Download progress: ${receivedSize} of ${totalSize}`);
});
downloadTask.on('complete', () => {
console.info('Download completed');
});
}).catch((error) => {
console.error('Download failed', error);
});
}
具体逻辑如下:
- 定义下载配置:
- 创建
downloadConfig
对象,包含文件的URL、本地保存路径和是否允许使用计量网络。
- 创建
- 创建下载任务:
- 使用
request.downloadFile
方法创建下载任务,并等待任务创建的结果。
- 使用
- 监听下载进度:
- 使用
downloadTask.on('progress')
方法监听下载进度,并在每次进度更新时输出已接收的字节数和总字节数。
- 使用
- 监听下载完成:
- 使用
downloadTask.on('complete')
方法监听下载完成事件,并在下载完成后输出成功信息。
- 使用
- 处理下载错误:
- 如果下载任务创建失败或下载过程中出现错误,输出错误信息。
3. 文件预览
文件预览功能可以通过PreviewKit
模块实现。以下是如何预览文件的代码:
import { filePreview } from '@kit.PreviewKit';
function previewFile(filePath) {
const fileUriObject = new fileUri.FileUri(filePath);
const fileInfo = {
title: 'File Name',
uri: fileUriObject.getFullDirectoryUri() + '/File Name',
mimeType: 'application/pdf', // 根据文件类型设置MIME类型
};
filePreview.openPreview(context, fileInfo).then(() => {
console.info('File preview succeeded');
}).catch((error) => {
console.error('File preview failed', error);
});
}
具体逻辑如下:
- 创建文件URI对象:
- 使用
fileUri.FileUri
构造函数创建fileUriObject
对象,传入文件路径。
- 使用
- 定义文件信息:
- 创建
fileInfo
对象,包含文件标题、完整URI和MIME类型。 - 根据文件类型设置MIME类型,例如PDF文件的MIME类型为
application/pdf
。
- 创建
- 预览文件:
- 使用
filePreview.openPreview
方法打开文件预览,传入当前上下文context
和文件信息。 - 如果预览成功,输出成功信息。
- 如果预览失败,输出错误信息。
- 使用
辅助工具方法
为了更好地管理和处理权限、文件操作等,我们可以编写一些辅助工具方法。
复制文件到缓存目录
import fs from '@ohos.file.fs';
import { JSON } from '@kit.ArkTS';
/**
* 复制文件到缓存目录下
* @param path :文件路径
* @param context :Context
* @returns Promise<string> 移动后文件路径
*/
export async function copyFileToCache(path: string, context: Context): Promise<string> {
try {
const file = fs.openSync(path, fs.OpenMode.READ_ONLY);
console.log('cwx-copyFileToCache-', JSON.stringify(file));
if (file) {
const fileDir = `${context.cacheDir}`; // 临时文件目录
const filename = path.split('/').pop(); // 获取文件名
const newPath = `${new Date().getTime()}_${filename}`;
const targetPath = `${fileDir}/${newPath}`;
fs.copyFileSync(file.fd, targetPath);
fs.closeSync(file.fd);
return newPath;
} else {
return '';
}
} catch (e) {
console.error('cwx-copyFileToCache-err-', JSON.stringify(e));
return '';
}
}
具体逻辑如下:
- 打开文件:使用
fs.openSync
方法以只读模式打开文件。 - 检查文件:如果文件成功打开,继续执行后续操作。
- 设置目标路径:
- 获取临时文件目录
context.cacheDir
。 - 生成新的文件名,使用时间戳和原始文件名。
- 组合目标路径。
- 获取临时文件目录
- 复制文件:使用
fs.copyFileSync
方法将文件从原始路径复制到目标路径。 - 关闭文件:使用
fs.closeSync
方法关闭文件。 - 返回新路径:返回复制后的新文件路径。
- 处理异常:如果在操作过程中出现异常,输出错误信息并返回空字符串。
校验应用是否授予权限
import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
/**
* 校验应用是否授予权限
* @param permission :权限名称数组
* @returns Promise<abilityAccessCtrl.GrantStatus> :权限状态
*/
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
const atManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus = 0;
// 获取应用程序的accessTokenID
try {
const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
const appInfo = bundleInfo.appInfo;
const tokenId = appInfo.accessTokenId;
// 校验应用是否被授予权限
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (err) {
console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
具体逻辑如下:
- 创建权限管理器:使用
abilityAccessCtrl.createAtManager
方法创建权限管理器atManager
。 - 获取应用程序的
accessTokenID
:- 调用
bundleManager.getBundleInfoForSelf
方法获取应用程序的信息。 - 从返回的
bundleInfo
中提取appInfo
,并获取accessTokenId
。
- 调用
- 校验应用是否被授予权限:使用
atManager.checkAccessToken
方法校验应用是否被授予权限。 - 处理异常:如果在获取应用程序信息或校验权限过程中出现异常,输出错误信息。
- 返回权限状态:返回权限状态
grantStatus
。
检查用户权限
import { abilityAccessCtrl } from '@ohos.abilityAccessCtrl';
/**
* 检查用户权限
* @param permissions :权限名称数组
* @returns Promise<boolean> :是否授权成功
*/
export async function checkPermissions(permissions: Permissions[]): Promise<boolean> {
try {
const grantStatus = await checkAccessToken(permissions);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
} catch (e) {
return Promise.reject(e);
}
}
具体逻辑如下:
- 调用
checkAccessToken
函数:请求校验应用是否被授予权限。 - 检查权限状态:如果权限状态为
PERMISSION_GRANTED
,返回true
;否则,返回false
。 - 处理异常:如果在请求权限状态过程中出现异常,返回
Promise.reject
。
申请权限
import common from '@ohos.app.ability.common';
import { abilityAccessCtrl } from '@ohos.abilityAccessCtrl';
interface rejectObj {
code: number;
message: string;
}
/**
* 申请权限
* @param context :UIAbilityContext
* @param permissions :权限名称数组
* @returns Promise<boolean> :是否授权成功
*/
export async function applyPermission(context: common.UIAbilityContext, permissions: Permissions[]): Promise<boolean> {
const atManager = abilityAccessCtrl.createAtManager();
return new Promise((resolve, reject) => {
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
const grantStatus = data.authResults;
resolve(grantStatus.every(item => item === 0));
}).catch((err) => {
reject(err);
});
});
}
具体逻辑如下:
- 创建权限管理器:使用
abilityAccessCtrl.createAtManager
方法创建权限管理器atManager
。 - 请求用户权限:使用
atManager.requestPermissionsFromUser
方法请求用户权限,并传入当前上下文context
和权限名称数组permissions
。 - 检查权限状态:如果用户授予了所有请求的权限,
authResults
数组中的每个元素都应为0
,返回true
;否则,返回false
。 - 处理异常:如果在请求用户权限过程中出现异常,返回
Promise.reject
。
实战1——下载文件保存并或预览
我们实现一个方法,调用他下载一个网络资源、根据传参决定是下载并保存文件,还是下载后预览文件。
dowLoadFile(data:DownloadParam,handler: CompleteHandler,preview: boolean){
data = JSON.parse(data+'') as DownloadParam
let context = getContext(this) as common.UIAbilityContext;
let fileName = data.name
let filesDir = context.filesDir;
let filePath = filesDir + '/'+fileName
try {
fs.accessSync(filePath);
fs.unlinkSync(filePath);
} catch (err) {
}
try {
this.loaded=false//加载动画开关,根据自己代码实际情况设置即可
request.downloadFile(context.getApplicationContext(), {
url: data.url,
filePath: filePath,
enableMetered:true
}).then((downloadTask: request.DownloadTask) => {
downloadTask.on('progress', (receivedSize: number, totalSize: number)=>{
console.info("downloadFile-receivedSize:" + receivedSize + " totalSize:" + totalSize);
});
downloadTask.on('complete', async () => {
this.loaded=true
console.info('downloadFile-complete');
if(preview){
this.doPreview(data.name,filePath)
}else{
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE);
let arrayBuffer = new ArrayBuffer(99999999);
let readLen = fs.readSync(file.fd, arrayBuffer);
let buf = buffer.from(arrayBuffer, 0, readLen);
console.info(`downloadFile-file:${readLen}_${buf.toString()}`);
fs.closeSync(file);
const documentSaveOptions = new picker.DocumentSaveOptions();
documentSaveOptions.newFileNames = [data.name];
let uris: Array<string> = [];
let context = getContext(this) as common.Context;
const documentViewPicker = new picker.DocumentViewPicker(context);
documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => {
uris = documentSaveResult;
let uri = uris[0]
try{
let file = fs.openSync(uri, fs.OpenMode.READ_WRITE);
let writeLen: number = fs.writeSync(file.fd, arrayBuffer);
console.info('write data to file succeed and size is:' + writeLen);
fs.closeSync(file);
promptAction.showToast({
message: '下载成功,保存到:'+uri,
duration: 1000,
});
}catch (e){
console.log('copyFile-err-',JSON.stringify((e)))
}
console.info('documentViewPicker.save to file succeed and uris are:' + uris);
}).catch((err: BusinessError) => {
console.error(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`);
})
}
})
}).catch((err: BusinessError) => {
this.loaded=true
console.error('downloadFile-err-',JSON.stringify(err));
});
} catch (error) {
let err: BusinessError = error as BusinessError;
console.error(`Invoke downloadFile failed, code is ${err.code}, message is ${err.message}`);
}
}
这段代码定义了一个名为 dowLoadFile
的函数,用于下载文件并根据参数选择是否预览文件或保存文件。以下是对这段代码的详细解析:
函数签名
dowLoadFile(data: DownloadParam, handler: CompleteHandler, preview: boolean)
- data: DownloadParam - 包含下载文件所需参数的对象,如文件名和下载URL。
- handler: CompleteHandler - 完成下载后的回调函数。
- preview: boolean - 是否预览文件的标志。
参数解析
data
是一个DownloadParam
类型的对象,通常包含以下字段:name
- 文件名。
{url} - 文件的下载URL。
handler
是一个CompleteHandler
类型的函数,通常用于处理下载完成后的逻辑。preview
是一个布尔值,决定是否预览文件。
函数体
数据解析
data = JSON.parse(data+'') as DownloadParam
- 将
data
转换为字符串后解析为一个DownloadParam
对象。
- 将
获取上下文
let context = getContext(this) as common.UIAbilityContext;
- 获取当前上下文并将其类型转换为
common.UIAbilityContext
,用于访问应用的文件目录和其他上下文信息。
- 获取当前上下文并将其类型转换为
文件路径构建
let fileName = data.name let filesDir = context.filesDir; let filePath = filesDir + '/'+fileName
- 从
data
中提取文件名。 - 获取应用的文件目录路径。
- 构建完整的文件路径。
- 从
检查并删除已存在的文件
try { fs.accessSync(filePath); fs.unlinkSync(filePath); } catch (err) { }
- 使用
fs.accessSync
检查文件是否已存在。 - 如果文件存在,使用
fs.unlinkSync
删除文件。
- 使用
设置加载状态
this.loaded=false
- 将加载状态设置为
false
,表示下载尚未完成。
- 将加载状态设置为
下载文件
request.downloadFile(context.getApplicationContext(), { url: data.url, filePath: filePath, enableMetered: true }).then((downloadTask: request.DownloadTask) => {
- 使用
request.downloadFile
方法开始下载文件。 url
是文件的下载URL。filePath
是文件的保存路径。enableMetered
设置为true
,表示允许在计量网络下下载文件。
- 使用
处理下载进度
downloadTask.on('progress', (receivedSize: number, totalSize: number) => { console.info("downloadFile-receivedSize:" + receivedSize + " totalSize:" + totalSize); });
- 监听下载任务的
progress
事件,获取已下载的文件大小和总文件大小,并打印日志。
- 监听下载任务的
处理下载完成
downloadTask.on('complete', async () => { this.loaded=true console.info('downloadFile-complete');
- 监听下载任务的
complete
事件,表示下载已完成。 - 将加载状态设置为
true
,表示下载已完成。 - 打印下载完成的日志。
- 监听下载任务的
预览文件
if(preview){ this.doPreview(data.name, filePath) }
- 如果
preview
为true
,调用this.doPreview
方法预览文件。
- 如果
读取文件内容
else{ let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE); let arrayBuffer = new ArrayBuffer(99999999); let readLen = fs.readSync(file.fd, arrayBuffer); let buf = buffer.from(arrayBuffer, 0, readLen); console.info(`downloadFile-file:${readLen}_${buf.toString()}`); fs.closeSync(file); }
- 如果
preview
为false
,打开下载的文件。 - 创建一个
ArrayBuffer
对象,用于存储文件内容。 - 使用
fs.readSync
读取文件内容到ArrayBuffer
。 - 打印读取的文件内容长度和内容。
- 关闭文件。
- 如果
获取任务信息并保存文件
const taskInfo = await downloadTask.getTaskInfo(); console.info('downloadFile-task-complete:'+`status: ${taskInfo.status}`); const documentSaveOptions = new picker.DocumentSaveOptions(); documentSaveOptions.newFileNames = [data.name]; let uris: Array<string> = []; let context = getContext(this) as common.Context; const documentViewPicker = new picker.DocumentViewPicker(context); documentViewPicker.save(documentSaveOptions).then((documentSaveResult: Array<string>) => { uris = documentSaveResult; let uri = uris[0] try{ let file = fs.openSync(uri, fs.OpenMode.READ_WRITE); let writeLen: number = fs.writeSync(file.fd, arrayBuffer); console.info('write data to file succeed and size is:' + writeLen); fs.closeSync(file); promptAction.showToast({ message: '下载成功,保存到:'+uri, duration: 1000, }); }catch (e){ console.log('copyFile-err-',JSON.stringify((e))) } console.info('documentViewPicker.save to file succeed and uris are:' + uris); }).catch((err: BusinessError) => { console.error(`Invoke documentViewPicker.save failed, code is ${err.code}, message is ${err.message}`); })
- 获取下载任务的详细信息,并打印状态。
- 创建
DocumentSaveOptions
对象,设置新的文件名。 - 使用
DocumentViewPicker
保存文件。 - 如果保存成功,获取保存的文件URI。
- 打开保存的文件,将
ArrayBuffer
中的内容写入文件。 - 打印写入文件成功的日志。
- 使用
promptAction.showToast
显示下载成功的提示信息。 - 如果保存失败,捕获错误并打印日志。
处理下载错误
}).catch((err: BusinessError) => { this.loaded=true console.error('downloadFile-err-',JSON.stringify(err)); });
- 如果下载文件失败,捕获错误,将加载状态设置为
true
,并打印错误日志。
- 如果下载文件失败,捕获错误,将加载状态设置为
处理其他错误
} catch (error) { let err: BusinessError = error as BusinessError; console.error(`Invoke downloadFile failed, code is ${err.code}, message is ${err.message}`); }
- 捕获其他可能的错误,将错误信息打印到日志。
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/888b3b4256864d3d80cd04123f70230d.png
- 捕获其他可能的错误,将错误信息打印到日志。
下载成功效果图
实战2——选择图片或拍照上传
@Builder myBuilder() {
Column() {
List({ space: 1 }) {
ListItem() {
Text('拍照').fontSize(18).fontColor('#409EFF').margin({ left: 12 })
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(5)
.align(Alignment.Center)
.onClick(()=>{this.takePhoto()})
ListItem() {
Text('相册').fontSize(18).fontColor('#409EFF').margin({ left: 12 })
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(5)
.align(Alignment.Center)
.onClick(()=>{this.getPhoto()})
}
List({ space: 1 }) {
ListItem() {
Text('取消').fontSize(18).fontColor('#409EFF').margin({ left: 12 }).fontWeight(FontWeight.Bold)
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(5)
.align(Alignment.Center)
.onClick(()=>{this.modalShow=false})
}
.margin({top:5})
}
.width('100%')
.height('100%')
}
......
async getPhoto(){
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then(async(photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
this.modalShow=false;
this.loaded =false;
if (photoSelectResult.photoUris.length) {
try {
let path = photoSelectResult.photoUris[0]
// //复制图片到缓存目录(缓存目录才有读写权限)
let filePath = await copyFileToCache(photoSelectResult.photoUris[0],this.context)
if(filePath){
this.uploadImg(filePath)
}else{
this.loaded =true;
}
} catch (err) {
this.loaded =true;
}
}else{
this.loaded =true;
}
}).catch((err: BusinessError) => {
this.modalShow=false;
})
}
async takePhoto(){
try {
let pickerProfile: cameraPicker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
let pickerResult: cameraPicker.PickerResult = await cameraPicker.pick(this.context,
[cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.PHOTO], pickerProfile);
console.log('takePhoto',JSON.stringify(pickerResult))
if (pickerResult?.resultUri) {
//关闭弹窗
this.modalShow=false;
this.loaded =false;
try {
let path = pickerResult.resultUri
// //复制图片到缓存目录(缓存目录才有读写权限)
let filePath = await copyFileToCache(pickerResult.resultUri,this.context)
if(filePath){
this.uploadImg(filePath)
}else{
this.loaded =true;
}
} catch (err) {
this.loaded =true;
}
}else{
this.loaded =true;
}
} catch (error) {
let err = error as BusinessError;
console.error(`the pick call failed. error code: ${err.code}`);
}
}
uploadImg(filePath:string){
const formData = new FormData()
formData.append('file', `internal://cache/${filePath}`)
uploadImg<PermissionResponse>(formData,{
headers: { 'Content-Type': 'multipart/form-data' },
context: getContext(this),
}).then(res=>{
console.log('cwx-uploadImg-',JSON.stringify(res))
if(res.data&&res.data.url){
promptAction.showToast({
message: '上传成功!',
duration: 1000,
});
this.handlerUploadCallBack?.complete(JSON.stringify({url:res.data.url}))
}
}).finally(()=>{
this.modalShow=false;
this.loaded = true;
})
}
这段代码定义了一个用于选择照片(拍照或从相册选择)并上传的界面和相关的逻辑处理。以下是详细的代码分析:
函数签名
@Builder myBuilder()
- 使用@Builder
装饰器定义一个构建器方法myBuilder
,用于构建UI界面。async getPhoto()
- 从相册选择照片并上传。async takePhoto()
- 拍照并上传。uploadImg(filePath: string)
- 上传照片到服务器。
myBuilder
方法
@Builder myBuilder() {
Column() {
List({ space: 1 }) {
ListItem() {
Text('拍照').fontSize(18).fontColor('#409EFF').margin({ left: 12 })
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(5)
.align(Alignment.Center)
.onClick(() => { this.takePhoto() })
ListItem() {
Text('相册').fontSize(18).fontColor('#409EFF').margin({ left: 12 })
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(5)
.align(Alignment.Center)
.onClick(() => { this.getPhoto() })
}
List({ space: 1 }) {
ListItem() {
Text('取消').fontSize(18).fontColor('#409EFF').margin({ left: 12 }).fontWeight(FontWeight.Bold)
}
.width('100%')
.height(56)
.backgroundColor('#FFFFFF')
.borderRadius(5)
.align(Alignment.Center)
.onClick(() => { this.modalShow = false })
}
.margin({ top: 5 })
}
.width('100%')
.height('100%')
}
Column 布局
- 使用
Column
布局容器,将内容垂直排列。 - 设置
Column
的宽度和高度为100%,使其占满整个屏幕。
- 使用
第一个 List 布局
- 使用
List
布局容器,设置space
为1,表示列表项之间的间距。 - 包含两个
ListItem
元素,分别用于拍照和选择相册中的照片。
- 使用
ListItem 1 - 拍照
- 使用
Text
组件显示“拍照”文字,设置字体大小为18,字体颜色为#409EFF
,左边距为12。 - 设置
ListItem
的宽度为100%,高度为56,背景色为#FFFFFF
,圆角为5,居中对齐。 - 绑定
onClick
事件,调用this.takePhoto
方法。
- 使用
ListItem 2 - 相册
- 使用
Text
组件显示“相册”文字,设置字体大小为18,字体颜色为#409EFF
,左边距为12。 - 设置
ListItem
的宽度为100%,高度为56,背景色为#FFFFFF
,圆角为5,居中对齐。 - 绑定
onClick
事件,调用this.getPhoto
方法。
- 使用
第二个 List 布局
- 使用
List
布局容器,设置space
为1,表示列表项之间的间距。 - 包含一个
ListItem
元素,用于取消操作。
- 使用
ListItem 3 - 取消
- 使用
Text
组件显示“取消”文字,设置字体大小为18,字体颜色为#409EFF
,左边距为12,字体加粗。 - 设置
ListItem
的宽度为100%,高度为56,背景色为#FFFFFF
,圆角为5,居中对齐。 - 绑定
onClick
事件,将this.modalShow
设置为false
,关闭弹窗。
- 使用
设置第二个 List 的外边距
- 设置
List
的上边距为5。
- 设置
getPhoto
方法
async getPhoto() {
const photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
photoViewPicker.select(photoSelectOptions).then(async (photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
this.modalShow = false;
this.loaded = false;
if (photoSelectResult.photoUris.length) {
try {
let path = photoSelectResult.photoUris[0]
// //复制图片到缓存目录(缓存目录才有读写权限)
let filePath = await copyFileToCache(photoSelectResult.photoUris[0], this.context)
if (filePath) {
this.uploadImg(filePath)
} else {
this.loaded = true;
}
} catch (err) {
this.loaded = true;
}
} else {
this.loaded = true;
}
}).catch((err: BusinessError) => {
this.modalShow = false;
})
}
创建照片选择选项
- 使用
photoAccessHelper.PhotoSelectOptions
创建照片选择选项对象。 - 设置
MIMEType
为IMAGE_TYPE
,表示只选择图片类型。 - 设置
maxSelectNumber
为1,表示最多选择一张图片。
- 使用
创建照片选择器
- 使用
photoAccessHelper.PhotoViewPicker
创建照片选择器对象。
- 使用
选择照片
- 调用
photoViewPicker.select
方法选择照片,传入选择选项。 - 使用
then
处理选择结果。 - 关闭弹窗并将加载状态设置为
false
。 - 检查选择结果中是否有照片URI。
- 使用
copyFileToCache
方法将选择的照片复制到缓存目录。 - 如果复制成功,调用
this.uploadImg
方法上传照片。 - 如果复制失败或没有选择照片,将加载状态设置为
true
。
- 调用
处理选择错误
- 使用
catch
捕获选择照片时的错误,并将弹窗显示状态设置为false
。
- 使用
takePhoto
方法
async takePhoto() {
try {
let pickerProfile: cameraPicker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
let pickerResult: cameraPicker.PickerResult = await cameraPicker.pick(this.context,
[cameraPicker.PickerMediaType.PHOTO, cameraPicker.PickerMediaType.PHOTO], pickerProfile);
console.log('takePhoto', JSON.stringify(pickerResult))
if (pickerResult?.resultUri) {
//关闭弹窗
this.modalShow = false;
this.loaded = false;
try {
let path = pickerResult.resultUri
// //复制图片到缓存目录(缓存目录才有读写权限)
let filePath = await copyFileToCache(pickerResult.resultUri, this.context)
if (filePath) {
this.uploadImg(filePath)
} else {
this.loaded = true;
}
} catch (err) {
this.loaded = true;
}
} else {
this.loaded = true;
}
} catch (error) {
let err = error as BusinessError;
console.error(`the pick call failed. error code: ${err.code}`);
}
}
创建相机选择配置
- 使用
cameraPicker.PickerProfile
创建相机选择配置对象,设置相机位置为后置摄像头。
- 使用
调用相机选择器
- 使用
cameraPicker.pick
方法调用相机,传入选择配置。 - 使用
then
处理拍照结果。 - 打印拍照结果的日志。
- 使用
处理拍照结果
- 检查拍照结果中是否有照片URI。
- 如果有照片URI,关闭弹窗并将加载状态设置为
false
。 - 使用
copyFileToCache
方法将拍照的照片复制到缓存目录。 - 如果复制成功,调用
this.uploadImg
方法上传照片。 - 如果复制失败或没有拍照,将加载状态设置为
true
。
处理拍照错误
- 使用
catch
捕获拍照时的错误,并将错误信息打印到日志中。
- 使用
uploadImg
方法
uploadImg(filePath: string) {
const formData = new FormData()
formData.append('file', `internal://cache/${filePath}`)
uploadImg<PermissionResponse>(formData, {
headers: { 'Content-Type': 'multipart/form-data' },
context: getContext(this),
}).then(res => {
console.log('cwx-uploadImg-', JSON.stringify(res))
if (res.data && res.data.url) {
promptAction.showToast({
message: '上传成功!',
duration: 1000,
});
this.handlerUploadCallBack?.complete(JSON.stringify({ url: res.data.url }))
}
}).finally(() => {
this.modalShow = false;
this.loaded = true;
})
}
创建表单数据
- 使用
FormData
创建表单数据对象。 - 将文件路径添加到表单数据中,路径前缀为
internal://cache/
,表示文件在缓存目录中。
- 使用
上传文件
- 使用
uploadImg
方法上传文件,传入表单数据和配置对象。 - 配置对象中设置请求头为
multipart/form-data
,并传入上下文。
- 使用
处理上传结果
- 使用
then
处理上传结果。 - 打印上传结果的日志。
- 检查上传结果中是否有URL。
- 如果有URL,显示上传成功的提示信息,并调用上传完成的回调函数。
- 使用
finally
在上传完成时关闭弹窗并将加载状态设置为true
。
- 使用