nui-app中云存储之图片上传删除

nui-app中云存储之图片上传删除

流程详解:

详细箭头流程说明:

  1. 上传阶段

    前端选择文件 → 调用uni.chooseImage() → 获取本地临时路径 → 调用uniCloud.uploadFile() → 文件存入云存储 → 返回云文件ID(cloud://) → 前端保存ID
    
  2. 记录阶段

    前端获得fileID → 调用云函数 → 云函数验证文件 → 将文件ID+URL存入数据库 → 返回成功结果 → 前端更新UI显示
    
  3. 删除阶段

    注意:

    uniCloud.deleteFile()客户端删除云存储文件。

    • 仅腾讯云支持此API,且使用时,需搭配腾讯云提供的自定义登录和权限设置使用;阿里云、支付宝云不支持此API。

    • 不建议使用此API。删除云存储文件是一个高危操作,应该由云函数进行权限校验后由云函数来删除云存储的文件。

    前端触发删除 → 携带fileID调用云函数 → 云函数校验权限 → 调用uniCloud.deleteFile() → 删除云存储文件 → 更新数据库状态 → 返回删除结果 → 前端移除UI元素
    

关键箭头节点说明:

前端组件 → 用户操作 → 云存储 → 云函数 → 数据库
↑           ↓           ↑         ↓         |
|___________|___________|_________|_________|

完整流程示例:

用户选择文件
uni.chooseImage获取临时路径
uniCloud.uploadFile上传
上传成功?
获取cloud://格式fileID
提示上传失败
调用云函数保存记录
数据库存储fileID+meta
前端显示文件
用户点击删除
携带fileID调用云函数
校验文件存在性和权限
允许删除?
uniCloud.deleteFile
返回权限错误
更新数据库状态
返回删除成功
前端移除文件显示

注意事项箭头提醒:

开发注意 → 文件ID必须为cloud://格式 → 删除前必须校验权限 → 重要操作需记录日志
           ↑                    ↑                    ↑
           |____________________|____________________|

错误处理流程:

删除失败 → 检查fileID格式 → 确认文件是否存在 → 验证云函数权限 → 查看云存储日志
↑______↓    ↑__________↓    ↑_____________↓    ↑___________↓

代码展示讲解

.html代码

<!-- 活动图片上传模块 -->
<view class="upload-container">
    <!-- 图片预览区域 -->
    <view v-if="tempFilePaths.length" class="preview-area">
        <!-- 循环显示已上传图片 -->
        <image 
            v-for="(item, index) in tempFilePaths" 
            :key="index" 
            :src="item" 
            mode="aspectFill"
            @click="previewImage(index)" 
            @longpress="deleteImage(index)" />
        
        <!-- 当未达到最大上传数量时显示添加按钮 -->
        <view v-if="tempFilePaths.length < maxCount" class="add-btn" @click="chooseImage">
            <uni-icons type="plusempty" size="30" color="#999"></uni-icons>
        </view>
    </view>

    <!-- 上传按钮(当没有图片时显示) -->
    <button v-else type="primary" :loading="uploading" @click="chooseImage">
        <text v-if="!uploading">上传图片</text>
        <text v-else>上传中...</text>
    </button>

    <!-- 上传进度条 -->
    <progress v-if="uploading" :percent="uploadProgress" stroke-width="3" show-info />
</view>

.js代码

注意:这里的 uni.chooseImage API的response结果的参数中的name、type仅H5支持的所以要对结果进行处理
在这里插入图片描述

// 导入云对象
// 使用 uniCloud.importObject 方法导入名为 'actives' 的云对象
// 云对象通常用于调用云函数或操作云数据库
const active = uniCloud.importObject('actives') // 第一步导入云对象

// 定义组件的属性(props)
// 使用 defineProps 定义组件接收的外部传入属性
const props = defineProps({
	// 最大上传数量
	// 类型为 Number,默认值为 9
	// 用于限制用户可以选择的最大图片数量
	maxCount: {
		type: Number,
		default: 9
	}, // 最大上传数量

	// 允许的文件类型
	// 类型为 Array,默认值为 ['image/png', 'image/jpeg']
	// 用于限制用户可以选择的文件类型
	fileTypes: {
		type: Array,
		default: () => ['image/png', 'image/jpeg']
	}, // 允许类型

	// 单文件最大大小(单位:MB)
	// 类型为 Number,默认值为 10
	// 用于限制用户上传的单个文件大小
	maxSize: {
		type: Number,
		default: 10
	}, // 单文件大小(MB)

	// 云存储路径
	// 类型为 String,默认值为 'uploads'
	// 用于指定文件在云存储中的存储路径
	cloudPath: {
		type: String,
		default: 'uploads'
	} // 云存储路径
})
// 定义事件发射器,用于触发 upload-success 和 upload-fail 事件
const emit = defineEmits(['upload-success', 'upload-fail'])

// 存储临时文件路径的数组
const tempFilePaths = ref([])
// 标记是否正在上传
const uploading = ref(false)
// 上传进度
const uploadProgress = ref(0)

// 选择图片
const chooseImage = async () => {
	try {
		// 调用 uni.chooseImage 方法选择图片
		// count:最多可选择的图片数量,根据 props.maxCount 和已选择的图片数量计算
		// sizeType:指定返回的图片格式,这里选择压缩后的图片
		// sourceType:选择图片的来源,支持相册和相机
		// extension:限制选择的文件类型,由 props.fileTypes 指定
		const res = await uni.chooseImage({
			count: props.maxCount - tempFilePaths.value.length,
			sizeType: ['compressed'],
			sourceType: ['album', 'camera'],
			extension: props.fileTypes
		})
		// 统一处理不同平台的文件对象
			const normalizedFiles = res.tempFiles.map(file => {
				// 小程序端可能没有name属性,需要生成
				const fileName = file.name ||
					`image_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`
				// 获取文件扩展名
				const ext = file.path.split('.').pop().toLowerCase()

				return {
					path: file.path,
					size: file.size,
					name: `${fileName}.${ext}`,
					type: ext
				}
			})
		// 校验文件大小
		// validateFiles 函数会校验文件类型和大小,返回校验通过的文件数组
		const validFiles = await validateFiles(normalizedFiles )
		// 将校验通过的文件路径添加到 tempFilePaths 数组中
		tempFilePaths.value = [...tempFilePaths.value, ...validFiles.map(f => f.path)]

		// 自动上传
		// 调用 uploadFiles 函数上传校验通过的文件
		await uploadFiles(validFiles)
	} catch (err) {
		// 如果选择图片或校验过程中出现错误,弹出提示
		uni.showToast({
			title: err.message,
			icon: 'none'
		})
	}
}

// 文件校验
const validateFiles = (files) => {
	return new Promise((resolve, reject) => {
		// 用于存储校验通过的文件
		const validFiles = []
		// 计算最大允许的文件大小(单位:字节)
		const maxBytes = props.maxSize * 1024 * 1024

		for (const file of files) {
			// 类型校验
			// 如果文件类型不在 props.fileTypes 中,直接抛出错误并终止校验
			if (!props.fileTypes.includes(file.type)) {
				return reject(new Error(`仅支持 ${props.fileTypes.join(',')} 格式`))
			}

			// 大小校验
			// 如果文件大小超过最大允许值,直接抛出错误并终止校验
			if (file.size > maxBytes) {
				return reject(new Error(`文件大小不能超过 ${props.maxSize}MB`))
			}

			// 如果文件通过校验,将其添加到 validFiles 数组中
			validFiles.push(file)
		}

		// 如果所有文件都通过校验,返回 validFiles 数组
		resolve(validFiles)
	})
}

// 上传到云存储
const uploadFiles = async (files) => {
	// 设置上传状态为 true,表示正在上传
	uploading.value = true
	// 初始化上传进度为 0
	uploadProgress.value = 0

	try {
		// 创建一个数组,用于存储每个文件的上传任务
		const uploadTasks = files.map(file => {
			// 返回一个 Promise,用于处理每个文件的上传逻辑
			return new Promise((resolve, reject) => {
				// 调用 uniCloud.uploadFile 方法上传文件到云存储
				// filePath:文件的本地路径
				// cloudPath:文件在云端的存储路径,使用时间戳和文件名避免冲突
				// onUploadProgress:监听上传进度事件,用于更新上传进度
				const task = uniCloud.uploadFile({
					filePath: file.path,
					cloudPath: `${props.cloudPath}/${Date.now()}_${file.name}`,
					onUploadProgress: (progressEvent) => {
						// 更新上传进度,计算公式为:已上传的字节数 / 总字节数 * 100
						uploadProgress.value = Math.round(
							(progressEvent.loaded / progressEvent.total) * 100
						)
					}
				})

				// 将上传任务的 Promise 结果绑定到 resolve 和 reject
				task.then(res => resolve(res)).catch(reject)
			})
		})

		// 使用 Promise.all 等待所有文件上传任务完成
		const results = await Promise.all(uploadTasks)
		console.log('文件上传————', results)
		// 所有文件上传成功后,触发 upload-success 事件,传递上传结果
		emit('upload-success', results)
	} catch (err) {
		// 如果上传过程中发生错误,打印错误信息
		console.error('上传失败:', err)
		// 触发 upload-fail 事件,传递错误信息
		emit('upload-fail', err)
	} finally {
		// 无论上传成功还是失败,都将上传状态设置为 false
		uploading.value = false
	}
}

// 图片预览
const previewImage = (index) => {
	// 调用 uni.previewImage 方法预览图片
	// current:当前预览的图片索引
	// urls:所有图片的路径数组
	uni.previewImage({
		current: index,
		urls: tempFilePaths.value
	})
}

// 删除图片
const deleteFile = async (index) => {
	// 弹出操作菜单,确认是否删除
	uni.showActionSheet({
		itemList: ['删除'],
		success: () => {
			// 从 tempFilePaths 数组中删除指定索引的图片路径
			tempFilePaths.value.splice(index, 1)
            // 获取要删除的文件 ID
			const fileID = tempFilePaths.value[index]
			try {
				// 调用云函数删除文件
				const res = await active.deleteFile(fileID)
				if (res.errCode === 0) {
					// 如果删除成功,打印日志并弹出提示
					console.log('删除成功', res.data)
					uni.showToast({
						title: '删除成功'
					})
				} else {
					// 如果删除失败,抛出错误
					throw new Error(res.errMsg)
				}
			} catch (err) {
				// 如果删除过程中出现错误,打印日志并弹出提示
				console.log('删除失败:', err)
				uni.showToast({
					title: '删除失败: ' + err.message,
					icon: 'none'
				})
			}
		}
	})
}

配套删除图片的云函数

这里我用的是云对象

module.exports = {
	async deleteFile(fileID) {
		// 参数校验
		if (!fileID) {
			return {
				errCode: 400,
				errMsg: '缺少fileID参数'
			}
		}

		try {
			// 实际删除操作
			const result = await uniCloud.deleteFile({
				fileList: fileID
			})

			return {
				errCode: 0,
				data: result
			}
		} catch (error) {
			return {
				errCode: 500,
				errMsg: '文件删除失败: ' + e
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值