HarmonyOS鸿蒙Next开发者技术支持-UniappX图片上传工具类封装

HarmonyOS鸿蒙Next开发者技术支持-UniappX图片上传工具类封装

一、 关键技术难点总结

本文将详细手把手带你在 UniappX 中如何封装一个图片上传的工具,使其方便在项目中随便使用,并且提供完整的代码示例,开发者可根据实际需求进行定制扩展。

1.1 问题说明

在 UniappX 跨平台应用开发中,实现图片上传功能是一个高频需求,但在实际项目中,开发者常面临以下痛点:

  1. 代码重复:每个需要上传图片的页面,都需要重新编写相册/相机调用、文件格式校验、大小限制、压缩处理等逻辑,导致代码冗余。
  2. 平台兼容:UniappX 虽然宣称跨端,但不同平台(如鸿蒙、iOS、Android)下的原生API特性或文件系统细节可能存在差异,直接使用基础API需额外处理兼容性,增加心智负担。
  3. 体验不一:散落在各处的上传逻辑,难以保证用户体验(如交互流程、错误提示)的一致性。
  4. 维护困难:当上传的业务规则(如允许的文件类型、大小上限)需要变更时,需要在所有相关页面逐一修改,维护成本高。

1.2 原因分析

上述问题的根源在于业务逻辑与界面组件的高度耦合,以及缺少一个抽象的、可复用的服务层。具体表现在:

  1. 逻辑未复用:图片选择、校验、压缩等核心流程是通用的,本应被封装为独立的工具函数,却被重复实现。
  2. 平台细节暴露:开发者需要直接面对 uni.chooseImageuni.showActionSheet等基础API的调用细节和参数配置,并自行处理可能存在的平台差异。
  3. 职责不清晰:页面或组件同时负责UI渲染和复杂的文件处理业务,违反了关注点分离原则,降低了代码的可读性和可测试性。

1.3 解决思路

为解决上述问题,提升开发效率和代码质量,我们采用“逻辑与UI分离、封装可复用工具”的思路:

  1. 核心工具封装:将图片选择、校验、压缩等纯逻辑功能剥离,封装成一个独立的、返回 Promise的工具函数(useImageUpload.uts)。该函数内部处理所有平台兼容性和业务规则,对外提供简洁统一的调用接口。
  2. UI组件化:创建一个专用的UI组件(ImageUploader.uvue),其职责仅限于图片预览、交互触发(点击上传、删除)和状态展示。组件通过调用封装好的工具函数来完成实际业务逻辑,实现UI与逻辑的解耦。
  3. 开箱即用:将工具函数与UI组件结合,提供一个功能完整、风格统一、支持响应式的图片上传组件,开发者可以直接在项目中引用,无需关心内部实现细节

1.4 解决方案

1.4.1 文件结构

src
├── components
│   ├── ImageUploader.uvue  // 图片上传UI组件
│   └── utils
│       └── useImageUpload.uts // 图片上传核心工具类

1.4.2 核心工具类 (useImageUpload.uts)

/* 选择图片并返回相关信息 */
export function chooseImage(): Promise<string> {
    return new Promise((resolve, reject) => {
        try {
            // 内部选择图片函数
            const selectImage = (sourceType: string) => {
                uni.chooseImage({
                    count: 1,
                    sizeType: ['compressed'], // 自动压缩
                    sourceType: [sourceType],
                    extension: ['.jpg', '.jpeg', '.png'],
                    success: (res: ChooseImageSuccess) => {
                        const file = res.tempFiles[0];
                        const type = file.path.substring(file.path.lastIndexOf('.') + 1).toLowerCase();

                        // 1. 文件类型校验
                        if (!/(png|jpeg|jpg)$/i.test(type)) {
                            const errMsg = '支持JPG/PNG/JPEG格式文件';
                            uni.showToast({ title: errMsg, icon: 'none' });
                            reject(new Error(errMsg));
                            return;
                        }

                        // 2. 文件大小校验 (示例为10MB)
                        if (file.size > 10 * 1024 * 1024) {
                            const errMsg = '文件不能超过 10MB';
                            uni.showToast({ title: errMsg, icon: 'none' });
                            reject(new Error(errMsg));
                            return;
                        }

                        // 3. 校验通过,返回临时文件路径
                        resolve(res.tempFilePaths[0]);
                    },
                    fail: (err) => {
                        reject(err);
                    }
                });
            };

            // 弹出选择器,让用户选择图片来源
            uni.showActionSheet({
                itemList: ['拍照', '从相册选择'],
                success: (res) => {
                    const sourceType = res.tapIndex === 0 ? 'camera' : 'album';
                    selectImage(sourceType);
                },
                fail: (err) => {
                    console.log('用户取消选择', err);
                    reject(new Error('用户取消'));
                }
            });
        } catch (err) {
            reject(err);
        }
    });
}

1.4.3 UI组件 (ImageUploader.uvue)

<template>
    <view>
        <view class="upload-container">
            <!-- 状态1: 未上传时,显示上传按钮 -->
            <l-svg
                v-if="!imagePath"
                class="upload-icon"
                src="/static/icon/upload.svg"
                @click="handleImageSelect"
            />
            <!-- 状态2: 已上传时,显示图片预览和删除按钮 -->
            <view v-else class="preview-wrapper">
                <image class="preview-image" :src="imagePath" mode="aspectFit" />
                <l-svg
                    class="delete-icon"
                    src="/static/icon/delete.svg"
                    @click="handleImageDelete"
                />
            </view>
        </view>
    </view>
</template>

<script setup lang="uts">
import { chooseImage } from './utils/useImageUpload.uts'

// 响应式图片路径
const imagePath = ref<string>('')

// 选择图片
const handleImageSelect = async (): Promise<void> => {
    try {
        const path = await chooseImage() // 调用工具函数
        imagePath.value = path
        console.log('上传成功,路径:', path)
        // 可根据需要,在此处触发父组件事件,如:emit('update', path)
    } catch (error) {
        console.error('图片上传失败:', error)
        // 错误已由工具函数统一提示,此处可进行额外处理
    }
}

// 删除图片
const handleImageDelete = (): void => {
    imagePath.value = ''
    console.log('图片已删除')
    // 可根据需要,在此处触发父组件事件
}
</script>

<style scoped lang="scss">
.upload-container {
    width: 144rpx;
    height: 144rpx;
    border: 2rpx dashed #ccc;
    border-radius: 8rpx;
    background-color: #f9f9f9;
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
}

.upload-icon {
    width: 48rpx;
    height: 48rpx;
    color: #999;
}

.preview-wrapper {
    width: 100%;
    height: 100%;
    position: relative;
}

.preview-image {
    width: 100%;
    height: 100%;
    border-radius: 6rpx;
}

.delete-icon {
    position: absolute;
    top: -10rpx;
    right: -10rpx;
    width: 40rpx;
    height: 40rpx;
    background: #fff;
    border-radius: 50%;
    box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
}
</style>

1.4.4 使用示例

在任意页面中,像使用普通组件一样引入即可:

<template>
    <view>
        <ImageUploader />
    </view>
</template>
<script setup lang="uts">
import ImageUploader from '@/components/ImageUploader.uvue'
</script>

更多关于HarmonyOS鸿蒙Next开发者技术支持-UniappX图片上传工具类封装的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

鸿蒙Next环境下,UniappX封装图片上传工具类需使用ArkTS/ArkUI。核心是调用系统picker选择图片,通过媒体库管理模块获取文件URI,再使用上传API(如@ohos.request)处理网络传输。注意权限声明与沙箱路径访问。

更多关于HarmonyOS鸿蒙Next开发者技术支持-UniappX图片上传工具类封装的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这是一个非常专业和实用的HarmonyOS Next + UniappX图片上传封装方案。您提出的“逻辑与UI分离”架构设计完全符合现代前端开发的最佳实践,特别是在跨平台场景下。

针对您的实现,我有几点技术补充:

  1. 关于HarmonyOS Next的适配:在纯HarmonyOS Next环境下,uni.chooseImage API底层会调用鸿蒙的PhotoViewPicker。您的封装已经考虑了平台差异,但建议在工具类中增加鸿蒙特有配置项的处理,比如鸿蒙支持的PhotoSelectOptions中的maxSelectNumberMIMEType等。

  2. 性能优化建议:对于大图片处理,可以考虑集成uni.compressImage进行更精细的压缩控制。在HarmonyOS上,可以结合image组件的onLoad回调获取实际图片尺寸,实现按需压缩。

  3. 类型安全增强:建议为chooseImage函数定义更完善的返回类型,例如:

interface ImageUploadResult {
  path: string
  size: number
  type: string
  width?: number
  height?: number
}
  1. 错误处理扩展:当前错误处理已覆盖基本场景,可考虑增加网络状态检查、存储权限预检等边界情况处理,特别是在HarmonyOS的权限模型下。

  2. 组件通信优化ImageUploader组件可以暴露更丰富的事件,如@select-start@progress(用于显示上传进度)、@error-detail等,提升组件可观测性。

您的方案将重复逻辑抽象为工具类,UI组件保持简洁,这种架构使得在HarmonyOS Next项目中进行功能扩展(如添加视频选择、文件预览等)非常容易,只需在工具类中增加对应方法即可。

代码结构清晰,文档详细,是高质量的HarmonyOS Next开发实践范例。

回到顶部