nui-app中云存储之图片上传删除
流程详解:
详细箭头流程说明:
-
上传阶段
前端选择文件 → 调用uni.chooseImage() → 获取本地临时路径 → 调用uniCloud.uploadFile() → 文件存入云存储 → 返回云文件ID(cloud://) → 前端保存ID
-
记录阶段
前端获得fileID → 调用云函数 → 云函数验证文件 → 将文件ID+URL存入数据库 → 返回成功结果 → 前端更新UI显示
-
删除阶段
注意:
uniCloud.deleteFile()客户端删除云存储文件。
-
仅腾讯云支持此API,且使用时,需搭配腾讯云提供的自定义登录和权限设置使用;阿里云、支付宝云不支持此API。
-
不建议使用此API。删除云存储文件是一个高危操作,应该由云函数进行权限校验后由云函数来删除云存储的文件。
前端触发删除 → 携带fileID调用云函数 → 云函数校验权限 → 调用uniCloud.deleteFile() → 删除云存储文件 → 更新数据库状态 → 返回删除结果 → 前端移除UI元素
-
关键箭头节点说明:
前端组件 → 用户操作 → 云存储 → 云函数 → 数据库
↑ ↓ ↑ ↓ |
|___________|___________|_________|_________|
完整流程示例:
注意事项箭头提醒:
开发注意 → 文件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
}
}
}
}