本文介绍的是如何将文件上传到服务器,这里将用上传图片作为示例。
上传图片首先我们得先从相册中拿到图片,这里就用到了photoAccessHelper模块(官方文档)
首先,我们先从相册中选择图片。
使用photoAccessHelper.PhotoViewPicker()来获取相册选择器对象,并通过对象中的select方法来选择图片。
select方法有两个返回值,其中photoUris是我们需要用到的。
photoUris | Array | 是 | 是 | 返回图库选择后的媒体文件的uri数组,此uri数组只能通过临时授权的方式调用photoAccessHelper.getAssets接口去使用,具体使用方式参见用户文件uri介绍中的媒体文件uri的使用方式。 |
isOriginalPhoto | boolean | 是 | 是 | 返回图库选择后的媒体文件是否为原图。 |
async selectImage(){
let photoPicker = new photoAccessHelper.PhotoViewPicker()
const res = await photoPicker.select({
// 选择相册文件 的类型,这里为 IMAGE_TYPE 图片类型
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
// 最多选几张
maxSelectNumber: 3
})
AlertDialog.show({ message: JSON.stringify(res) })
}
拿到图片后,我们先把图片渲染到界面上
// 创建一个数组存uri ForEach渲染到UI上
interface ImageList {
/** 图片url */
url: string;
}
@State List: ImageList[] = []
// 将photoUris中的url通过map存到List里 [{ url: photoUris数组里的值 }]
this.List.push(…res.photoUris.map( url => {
return { url } as ImageList
} ))
ForEach(this.List, (item: ImageList, i) => {
Image(item.url)
.width(95)
.height(95)
})
封装一个自定义弹窗,里边放上Swiper,将数组传进去,实现点击图片放大预览,并能左右滑动切换,完整代码:
import { photoAccessHelper } from '@kit.MediaLibraryKit'
interface ImageList {
/** 图片url */
url: string;
}
@Entry
@Component
struct Index {
@State List: ImageList[] = []
index:number = 0
diallor: CustomDialogController = new CustomDialogController({
builder: HmPreview({urls: this.List , selectIndex: this.index}),
customStyle: true
})
async selectImage(){
let photoPicker = new photoAccessHelper.PhotoViewPicker()
const res = await photoPicker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 3
})
this.List.push(…res.photoUris.map( url => {
return { url } as ImageList
} ))
}
build() {
Column(){
Button('拉起相册')
.onClick(() => {
this.selectImage()
})
ForEach(this.List, (item: ImageList, i) => {
Image(item.url)
.width(95)
.height(95)
.onClick(() => {
this.index = i
this.diallor.open()
})
})
}
.height('100%')
.width('100%')
}
}
@CustomDialog
@Component
struct HmPreview {
controller: CustomDialogController
// 支持多张图片预览 给多张地址 给一个需要预览的索引
urls: ImageList[] = [] // 多张地址
selectIndex:number = –1 // 当前索引
build() {
Column() {
Swiper() {
ForEach(this.urls, (url: ImageList) => {
Image(url.url)
.width("100%") // 只给宽度 不给高度 让自己撑开
.onClick(() => {
this.controller.close()
})
})
}
.indicator(false) // 去掉点的显示
.index(this.selectIndex) // 当前要看的是第几张图片
}
.justifyContent(FlexAlign.Center)
.width("100%")
.height("100%")
}
}
因为上传文件只能从沙箱文件中拷贝,所以接下来封装一个API将传进来的图片拷贝到沙箱里。
获取与页面上下文组件关联的Context对象,并使用里边的cacheDir方法获取应用文件路径。
//list: ImageList[] 为传进来的
export const UploadFile = async (list: ImageList[]) => {
const saveDir = getContext().cacheDir // 存储在沙箱的目录
const fileParams: request.File[] = [] // 需要提交的参数
}
使用fileIo相册管理器中的openSync方法,打开并读取相册文件,将获取到的图片url传进方法里,返回一个File对象,其中的fd是图片文件的描述符(打开的文件描述符)我们需要传到copyFileSync(以同步方法复制文件)方法里。
之后添加到提交参数里,添加完后将文件关闭。
list.forEach((item) => {
// 读取相册文件
const file = fileIo.openSync(item.url, fileIo.OpenMode.READ_ONLY)
// 将文件拷贝到沙箱目录
// 相册地址 使用UUID作为图片名
const uniqueName = util.generateRandomUUID() + '.jpg'
// 将相册文件拷贝到沙箱
fileIo.copyFileSync(file.fd, saveDir + '/' + uniqueName)
fileParams.push({
filename: uniqueName, // 文件名称
name: 'file', // 接口的参数名
type: 'jpg', // 文件后缀
uri: `internal://cache/${uniqueName}`// 应该是文件放到cache目录下 如果是cache协议 它会自动找这个文件
})
// 拷贝完关闭文件
fileIo.closeSync(file.fd)
})
完整代码:
// 将图片拷贝到沙箱目录
export const UploadFile = async (list: ImageList[]) => {
// 因为上传文件只能从沙箱文件中拷贝,所以我们需要把传过来的所有图片拷贝到沙箱中
const saveDir = getContext().cacheDir // 存储在沙箱的目录
const fileParams: request.File[] = [] // 需要提交的参数
list.forEach((item) => {
// 读取相册文件 打开的File对象。
const file = fileIo.openSync(item.url, fileIo.OpenMode.READ_ONLY)
// 将文件拷贝到沙箱目录
// 相册地址 使用UUID作为图片名
const uniqueName = util.generateRandomUUID() + '.jpg'
// 将相册文件拷贝到沙箱
fileIo.copyFileSync(file.fd, saveDir + '/' + uniqueName)
fileParams.push({
filename: uniqueName, // 文件名称
name: 'file', // 接口的参数名
type: 'jpg', // 文件后缀
uri: `internal://cache/${uniqueName}`// 应该是文件放到cache目录下 如果是cache协议 它会自动找这个文件
})
// 拷贝完关闭文件
fileIo.closeSync(file.fd)
})
AlertDialog.show({ message: JSON.stringify(fileParams) })
}
接下来封装一个上传请求的API,将图片提交到服务器。
这个API需要两个参数,一个是应用上下文,一个是要上传的文件列表(官方文档)。
定义API将上传任务的配置信息配置一下(官方文档)
export const uploadImage = async (context: Context, files: request.File[]) => {
let config: request.UploadConfig = {
url: '服务器地址',
method: 'POST',// 请求类型
header: {
Authorization: AppStorage.get(TOKEN_KEY) || "", // 应用中的token
"Content-Type": "multipart/form-data" // 上传文件的参数类型
// multipart/form-data 专门传文件的结构
},
files,
data: [] // 批量上传需要携带的参数 用不上
}
}
上传文件不一定会上传成功,也可能会上传失败,我们使用Promise管理一下成功状态和失败状态。 返回一个数组url,以便供其它接口使用。
return new Promise<ImageList[]>(async (resolve, reject) => {
let arr: ImageList[] = []
// 使用Promise方式,异步返回上传任务。 可通过 on方法订阅事件,获取任务状态
const task = await request.uploadFile(context, config) // 成功执行并不意味着 上传成功 只是任务创建成功
})
订阅事件获取任务状态
//订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
task.on("fail", () => {
// 上传失败
AlertDialog.show({
message: "上传失败"
})
reject(new Error("上传失败"))
})
// 每上传成功一次就会进来
task.on("headerReceive", (headers: object) => {
// 响应回来的数据
if (headers["body"]) {
// 转换为对象形式
const result = JSON.parse(headers["body"]) as ResponseData<string>
// result.code === 200为成功时的状态码
if (result.code === 200) {
arr.push({
url: result.data as string
})
}
}
})
//订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
task.on("complete", () => {
// 上传完成
// 也不能拿到上传成功的地址
// 走到这里认为 要返回的结构已经ok了
resolve(arr)
})
到这里,上传图片基本完成的差不多了,接着我们需要用try捕获上传时出现的一些错误
try{
//订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
task.on("fail", () => {
// 上传失败
AlertDialog.show({
message: "上传失败"
})
reject(new Error("上传失败"))
})
// 每上传成功一次就会进来
task.on("headerReceive", (headers: object) => {
// 响应回来的数据
if (headers["body"]) {
// 转换为对象形式
const result = JSON.parse(headers["body"]) as ResponseData<string>
// result.code === 200为成功时的状态码
if (result.code === 200) {
arr.push({
url: result.data as string
})
}
}
})
//订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
task.on("complete", () => {
// 上传完成
// 也不能拿到上传成功的地址
// 走到这里认为 要返回的结构已经ok了
resolve(arr)
})
}catch(error) {
AlertDialog.show({
message: error.message // 提示错误消息
})
reject(error)
}
上传请求API完整代码:
export const uploadImage = async (context: Context, files: request.File[]) => {
let config: request.UploadConfig = {
url: '服务器地址',
method: 'POST',// 请求类型
header: {
Authorization: AppStorage.get(TOKEN_KEY) || "", // 应用中的token
"Content-Type": "multipart/form-data" // 上传文件的参数类型
// multipart/form-data 专门传文件的结构
},
files,
data: [] // 批量上传需要携带的参数 用不上
}
return new Promise<ImageList[]>(async (resolve, reject) => {
let arr: ImageList[] = []
// 使用Promise方式,异步返回上传任务。 可通过 on方法订阅事件,获取任务状态
const task = await request.uploadFile(context, config) // 成功执行并不意味着 上传成功 只是任务创建成功
try{
//订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
task.on("fail", () => {
// 上传失败
AlertDialog.show({
message: "上传失败"
})
reject(new Error("上传失败"))
})
// 每上传成功一次就会进来
task.on("headerReceive", (headers: object) => {
// 响应回来的数据
if (headers["body"]) {
// 转换为对象形式
const result = JSON.parse(headers["body"]) as ResponseData<string>
// result.code === 200为成功时的状态码
if (result.code === 200) {
arr.push({
url: result.data as string
})
}
}
})
//订阅的事件类型,取值为'complete',表示上传任务完成;取值为'fail',表示上传任务失败
task.on("complete", () => {
// 上传完成
// 也不能拿到上传成功的地址
// 走到这里认为 要返回的结构已经ok了
resolve(arr)
})
}catch(error) {
AlertDialog.show({
message: error.message // 提示错误消息
})
reject(error)
}
})
}
最后我们将上传请求API放到拷贝沙箱API里
// 将图片拷贝到沙箱目录
export const UploadFile = async (list: ImageList[]) => {
// 因为上传文件只能从沙箱文件中拷贝,所以我们需要把传过来的所有图片拷贝到沙箱中
const saveDir = getContext().cacheDir // 存储在沙箱的目录
const fileParams: request.File[] = [] // 需要提交的参数
list.forEach((item) => {
// 读取相册文件 打开的File对象。
const file = fileIo.openSync(item.url, fileIo.OpenMode.READ_ONLY)
// 将文件拷贝到沙箱目录
// 相册地址 使用UUID作为图片名
const uniqueName = util.generateRandomUUID() + '.jpg'
// 将相册文件拷贝到沙箱
fileIo.copyFileSync(file.fd, saveDir + '/' + uniqueName)
fileParams.push({
filename: uniqueName, // 文件名称
name: 'file', // 接口的参数名
type: 'jpg', // 文件后缀
uri: `internal://cache/${uniqueName}`// 应该是文件放到cache目录下 如果是cache协议 它会自动找这个文件
})
// 拷贝完关闭文件
fileIo.closeSync(file.fd)
})
// AlertDialog.show({ message: JSON.stringify(fileParams) })
return await uploadImage(getContext(), fileParams)
}
有不足之处欢迎大家指点。
评论前必须登录!
注册