鸿蒙中图片上传
·
import { image } from '@kit.ImageKit'
import fs from '@ohos.file.fs';
import { picker } from '@kit.CoreFileKit';
import { http } from '@kit.NetworkKit';
import { BusinessError, request } from '@kit.BasicServicesKit';
export class BaseHttpResponse<T> {
code: number;
msg: string;
data: T;
constructor(code: number, msg: string, data: T) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
// 定义类型
interface UploadResponse {
body: string
}
/**
* 上传响应数据类型
*/
export interface UploadData {
/**
* 上传成功的图片-在线地址
*/
url: string;
/**
* OSS文件ID
*/
ossId: string;
/**
* 图片宽高比
*/
aspectRatio: string;
}
/**
* 原始响应数据结构
*/
interface RawUploadResponse {
code: number;
msg: string;
data: UploadData;
}
/**
* 选择的图片信息
*/
export interface SelectedImageInfo {
/**
* 唯一ID
*/
id: string;
/**
* 原始图片URI
*/
originalUri: string;
/**
* 临时文件路径
*/
tempFilePath: string;
/**
* 文件名
*/
fileName: string;
/**
* 文件大小(字节)
*/
fileSize: number;
/**
* 选择时间戳
*/
selectTime: number;
}
/**
* 图片选择类型枚举
*/
export enum ImageSelectType {
COVER = 'cover', // 封面图片
MULTIPLE = 'multiple' // 多张图片
}
/**
* 图片上传API类 - 专门处理接口请求
*/
export class ImageUploadAPI {
private static readonly UPLOAD_URL = '';
/**
* 上传单张图片到服务器
* @param context 页面上下文
* @param files 文件信息数组
* @param aspectRatio 宽高比
* @returns Promise<BaseHttpResponse<UploadData>> 返回服务器响应
*/
static async uploadImage(
context: Context,
files: Array<request.File>,
aspectRatio: string = "4:3"
): Promise<BaseHttpResponse<UploadData>> {
return new Promise((resolve, reject) => {
request.uploadFile(context, {
url: ImageUploadAPI.UPLOAD_URL,
method: http.RequestMethod.POST,
header: {
contentType: 'multipart/form-data',
},
files,
data: [
{
name: 'aspectRatio',
value: aspectRatio
}
]
})
.then((res) => {
res.on('headerReceive', (value) => {
try {
const uploadRes = (value as UploadResponse)
// 使用明确的类型进行JSON解析
const responseData = JSON.parse(uploadRes.body) as RawUploadResponse
// 使用BaseHttpResponse包装响应数据
const response = new BaseHttpResponse<UploadData>(
responseData.code,
responseData.msg,
responseData.data
)
resolve(response)
} catch (parseError) {
reject(new Error('响应解析失败'))
}
})
})
.catch((error: BusinessError) => {
reject(error)
})
})
}
/**
* 批量上传多张图片
* @param context 页面上下文
* @param filesList 文件信息数组列表
* @param aspectRatio 宽高比
* @returns Promise<BaseHttpResponse<UploadData>[]> 返回所有上传结果
*/
static async uploadMultipleImages(
context: Context,
filesList: Array<request.File>[],
aspectRatio: string = "4:3"
): Promise<BaseHttpResponse<UploadData>[]> {
const uploadPromises = filesList.map(files =>
ImageUploadAPI.uploadImage(context, files, aspectRatio)
);
return Promise.all(uploadPromises);
}
}
/**
* 多图片上传结果
*/
export interface MultiUploadResult {
success: UploadData[]; // 成功上传的图片数据列表(包含url、ossId、aspectRatio)
failed: string[]; // 失败的文件名列表
total: number; // 总文件数
successCount: number; // 成功数量
failedCount: number; // 失败数量
}
/**
* 图片上传管理器 - 处理业务逻辑
*/
export class ImageUploadManager {
// 图片选择配置
private static photoSelectOptions = new picker.PhotoSelectOptions();
static {
ImageUploadManager.photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
ImageUploadManager.photoSelectOptions.maxSelectNumber = 1;
}
// 分别存储不同类型的图片信息
private static coverImage: SelectedImageInfo | null = null; // 封面图片
private static multipleImages: SelectedImageInfo[] = []; // 多张图片
/**
* 获取封面图片信息
* @returns SelectedImageInfo | null 封面图片信息
*/
static getCoverImage(): SelectedImageInfo | null {
return ImageUploadManager.coverImage;
}
/**
* 获取多张图片信息列表
* @returns SelectedImageInfo[] 多张图片信息数组
*/
static getMultipleImages(): SelectedImageInfo[] {
return ImageUploadManager.multipleImages;
}
/**
* 清空封面图片
*/
static clearCoverImage(): void {
if (ImageUploadManager.coverImage) {
// 清理封面图片的临时文件
ImageUploadManager.cleanupTempFile(ImageUploadManager.coverImage.tempFilePath);
ImageUploadManager.coverImage = null;
}
}
/**
* 清空多张图片
*/
static clearMultipleImages(): void {
// 清理多张图片的临时文件
ImageUploadManager.multipleImages.forEach(imageInfo => {
ImageUploadManager.cleanupTempFile(imageInfo.tempFilePath);
});
ImageUploadManager.multipleImages = [];
}
/**
* 清空所有图片
*/
static clearAllImages(): void {
ImageUploadManager.clearCoverImage();
ImageUploadManager.clearMultipleImages();
}
/**
* 页面离开时清理所有临时文件
* 建议在页面的 onPageHide 或 onDestroy 生命周期中调用
*/
static onPageLeave(): void {
console.log('页面离开,开始清理临时文件...');
ImageUploadManager.clearAllImages();
console.log('临时文件清理完成');
}
/**
* 根据类型获取图片信息
* @param type 图片选择类型
* @returns SelectedImageInfo[] | SelectedImageInfo | null 图片信息
*/
static getImagesByType(type: ImageSelectType): SelectedImageInfo[] | SelectedImageInfo | null {
switch (type) {
case ImageSelectType.COVER:
return ImageUploadManager.coverImage;
case ImageSelectType.MULTIPLE:
return ImageUploadManager.multipleImages;
default:
return null;
}
}
/**
* 选择封面图片
* @param context 页面上下文
* @param aspectRatio 宽高比,默认为 "4:3"
* @returns Promise<UploadData> 返回上传后的图片数据
*/
static async selectCoverImage(context: Context, aspectRatio: string = "4:3"): Promise<UploadData> {
return new Promise((resolve, reject) => {
const photoViewPicker = new picker.PhotoViewPicker();
photoViewPicker.select(ImageUploadManager.photoSelectOptions)
.then(res => {
const uri = res.photoUris[0]
console.log('选择的封面图片URI:', uri)
// 创建封面图片信息并存储
const imageInfo = ImageUploadManager.createImageInfo(uri, context);
ImageUploadManager.coverImage = imageInfo;
ImageUploadManager.uploadImage(context, uri, aspectRatio)
.then(resolve)
.catch(reject)
})
.catch((error: BusinessError) => {
console.error('选择封面图片失败:', error)
reject(error)
})
})
}
/**
* 选择多张图片(追加模式)
* @param context 页面上下文
* @param maxCount 最大选择数量,默认为9
* @param aspectRatio 宽高比,默认为 "4:3"
* @returns Promise<MultiUploadResult> 返回上传结果
*/
static async selectMultipleImages(
context: Context,
maxCount: number = 9,
aspectRatio: string = "4:3"
): Promise<MultiUploadResult> {
return new Promise((resolve, reject) => {
const multiSelectOptions = new picker.PhotoSelectOptions();
multiSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
multiSelectOptions.maxSelectNumber = maxCount;
const photoViewPicker = new picker.PhotoViewPicker();
photoViewPicker.select(multiSelectOptions)
.then(res => {
const uris = res.photoUris
console.log('选择的多张图片URIs:', JSON.stringify(uris))
console.log(`选择了 ${uris.length} 张图片`)
// 为每张图片创建信息并追加到现有数组
const imageInfos = uris.map(uri => ImageUploadManager.createImageInfo(uri, context));
ImageUploadManager.multipleImages.push(...imageInfos); // 追加而不是替换
console.log(`当前多张图片总数: ${ImageUploadManager.multipleImages.length}`);
ImageUploadManager.uploadMultipleImages(context, uris, aspectRatio)
.then(resolve)
.catch(reject)
})
.catch((error: BusinessError) => {
console.error('选择多张图片失败:', error)
reject(error)
})
})
}
/**
* 上传单张图片(直接上传指定URI)
* @param context 页面上下文
* @param uri 图片URI
* @param aspectRatio 宽高比,默认为 "4:3"
* @returns Promise<UploadData> 返回上传后的图片数据
*/
static async uploadImage(context: Context, uri: string, aspectRatio: string = "4:3"): Promise<UploadData> {
return ImageUploadManager.uploadSingleImage(context, uri, aspectRatio)
}
/**
* 批量上传多张图片(直接上传指定URI数组)
* @param context 页面上下文
* @param uris 图片URI数组
* @param aspectRatio 宽高比,默认为 "4:3"
* @returns Promise<MultiUploadResult> 返回上传结果
*/
static async uploadMultipleImages(
context: Context,
uris: string[],
aspectRatio: string = "4:3"
): Promise<MultiUploadResult> {
const result: MultiUploadResult = {
success: [],
failed: [],
total: uris.length,
successCount: 0,
failedCount: 0
};
// 并发上传所有图片
const uploadPromises = uris.map(async (uri, index) => {
try {
const uploadData = await ImageUploadManager.uploadSingleImage(context, uri, aspectRatio)
result.success.push(uploadData)
result.successCount++
console.log(`第 ${index + 1} 张图片上传成功:`, uploadData)
} catch (error) {
result.failed.push(`image_${Date.now()}_${index}.jpg`)
result.failedCount++
console.error(`第 ${index + 1} 张图片上传失败:`, error)
}
});
// 等待所有上传完成
await Promise.all(uploadPromises)
console.log(`批量上传完成: 成功 ${result.successCount} 张,失败 ${result.failedCount} 张`)
return result
}
/**
* 创建图片信息对象
* @param uri 图片URI
* @param context 页面上下文
* @returns SelectedImageInfo 图片信息对象
*/
private static createImageInfo(uri: string, context: Context): SelectedImageInfo {
const id = `img_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const fileName = `image_${Date.now()}.jpg`;
const tempFilePath = context.cacheDir + '/' + fileName;
// 获取文件大小
let fileSize = 0;
try {
const file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
const stat = fs.statSync(file.fd);
fileSize = stat.size;
fs.closeSync(file);
} catch (error) {
console.warn('获取文件大小失败:', error);
}
return {
id,
originalUri: uri,
tempFilePath,
fileName,
fileSize,
selectTime: Date.now()
};
}
/**
* 上传单张图片(内部方法)
* @param context 页面上下文
* @param uri 图片URI
* @param aspectRatio 宽高比
* @returns Promise<UploadData> 返回上传后的图片数据
*/
private static async uploadSingleImage(
context: Context,
uri: string,
aspectRatio: string
): Promise<UploadData> {
let tempFilePath: string | null = null;
try {
// 1. 处理临时文件
const files = await ImageUploadManager.prepareTempFile(context, uri);
tempFilePath = context.cacheDir + '/' + files[0].filename;
// 2. 调用API上传
const response = await ImageUploadAPI.uploadImage(context, files, aspectRatio);
// 3. 处理响应
if (response.code === 200) {
return response.data;
} else {
throw new Error(response.msg || '上传失败');
}
} catch (error) {
console.error('上传图片失败:', error);
throw new Error();
} finally {
// 4. 清理临时文件
if (tempFilePath) {
ImageUploadManager.cleanupTempFile(tempFilePath);
}
}
}
/**
* 处理临时文件 - 拷贝文件到缓存目录并准备上传数据
* @param context 页面上下文
* @param uri 图片URI
* @returns Promise<Array<request.File>> 返回文件信息数组
*/
private static async prepareTempFile(
context: Context,
uri: string
): Promise<Array<request.File>> {
return new Promise((resolve, reject) => {
let file: fs.File | null = null;
try {
// 1. 生成文件名
const fileName = `image_${Date.now()}.jpg`;
const tempFilePath = context.cacheDir + '/' + fileName;
// 2. 打开原文件
file = fs.openSync(uri, fs.OpenMode.READ_ONLY);
// 3. 拷贝文件到临时目录
fs.copyFileSync(file.fd, tempFilePath);
// 4. 关闭原文件
fs.closeSync(file);
file = null;
// 5. 创建文件信息数组
const files: Array<request.File> = [
{
filename: fileName,
type: 'jpg',
name: 'file',
uri: `internal://cache/${fileName}`
}
];
resolve(files);
} catch (error) {
// 确保文件句柄关闭
if (file) {
try {
fs.closeSync(file);
} catch (closeError) {
console.warn('关闭文件句柄失败:', closeError);
}
}
reject(error);
}
});
}
/**
* 清理临时文件
* @param filePath 文件路径
*/
private static cleanupTempFile(filePath: string): void {
try {
if (fs.accessSync(filePath)) {
fs.unlinkSync(filePath);
console.log('临时文件已清理:', filePath);
}
} catch (error) {
console.warn('清理临时文件失败:', filePath, error);
}
}
}
@Entry
@Component
struct Test {
@State pixel: image.PixelMap | undefined = undefined;
onPageHide() {
ImageUploadManager.onPageLeave();
}
build() {
Column() {
Button('选择封面图片')
.onClick(async () => {
try {
const context = getContext(this);
const uploadData = await ImageUploadManager.selectCoverImage(context);
// 获取封面图片信息
const coverImage = ImageUploadManager.getCoverImage();
console.log('封面图片信息:', JSON.stringify(coverImage));
AlertDialog.show({
message: `封面图片上传成功!\n图片地址: ${uploadData.url}\nOSS ID: ${uploadData.ossId}`
});
} catch (error) {
AlertDialog.show({
message: `操作失败: ${error.message || '未知错误'}`
});
}
})
Button('选择多张图片(追加)')
.onClick(async () => {
try {
const context = getContext(this);
const result = await ImageUploadManager.selectMultipleImages(context, 5);
// 获取多张图片信息
const multipleImages = ImageUploadManager.getMultipleImages();
console.log('多张图片信息:', JSON.stringify(multipleImages));
AlertDialog.show({
message: `多张图片上传完成!\n成功: ${result.successCount} 张\n失败: ${result.failedCount} 张\n当前总图片数量: ${multipleImages.length}`
});
} catch (error) {
AlertDialog.show({
message: `操作失败: ${error.message || '未知错误'}`
});
}
})
Button('查看封面图片信息')
.onClick(() => {
const coverImage = ImageUploadManager.getCoverImage();
if (!coverImage) {
AlertDialog.show({
message: '没有选择封面图片'
});
} else {
AlertDialog.show({
message: `封面图片信息:\n\nID: ${coverImage.id}\n文件名: ${coverImage.fileName}\n大小: ${coverImage.fileSize}字节\n选择时间: ${new Date(coverImage.selectTime).toLocaleString()}`
});
}
})
Button('查看多张图片信息')
.onClick(() => {
const multipleImages = ImageUploadManager.getMultipleImages();
if (multipleImages.length === 0) {
AlertDialog.show({
message: '没有选择多张图片'
});
} else {
const info = multipleImages.map((img, index) =>
`第${index +
1}张:\nID: ${img.id}\n文件名: ${img.fileName}\n大小: ${img.fileSize}字节\n选择时间: ${new Date(img.selectTime).toLocaleString()}`
).join('\n\n');
AlertDialog.show({
message: `多张图片信息 (共${multipleImages.length}张):\n\n${info}`
});
}
})
Button('清空封面图片')
.onClick(() => {
ImageUploadManager.clearCoverImage();
AlertDialog.show({
message: '已清空封面图片'
});
})
Button('清空多张图片')
.onClick(() => {
ImageUploadManager.clearMultipleImages();
AlertDialog.show({
message: '已清空多张图片'
});
})
Button('清空所有图片')
.onClick(() => {
ImageUploadManager.clearAllImages();
AlertDialog.show({
message: '已清空所有图片'
});
})
Button('手动清理临时文件')
.onClick(() => {
ImageUploadManager.onPageLeave();
AlertDialog.show({
message: '已手动清理所有临时文件'
});
})
}
.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
更多推荐



所有评论(0)